Я вважаю, що я змішав код C і C ++, коли мені не довелося; Це проблема і як виправити?


10

Передісторія / сценарій

Я почав писати додаток CLI суто в C (моя перша правильна програма C або C ++, яка не була "Hello World" або її варіацією). Приблизно на середині я працював із "рядками" введення користувача (масиви char), і я виявив об'єкт стримерного струму C ++. Я бачив, що я можу зберегти код за допомогою цих, тому використовував їх через додаток. Це означає, що я змінив розширення файлу на .cpp і тепер компілюю додаток g++замість gcc. Отже, виходячи з цього, я б сказав, що програма тепер технічно є додатком C ++ (хоча 90% + коду написано в тому, що я б назвав C, оскільки між двома мовами існує багато перехресних даних, враховуючи мій обмежений досвід роботи два). Це один .cpp-файл довжиною близько 900 рядків.

Важливі фактори

Я хочу, щоб програма була безкоштовною (як у грошах) і вільно розповсюджувалась та була корисною для всіх. Мене хвилює те, що хтось подивиться на код і щось подумає про це:

О, подивіться на кодування, це жахливо, ця програма не може мені допомогти

Коли потенційно це могло! Інша справа - ефективність коду (це програма для тестування підключення до Ethernet). Не повинно бути частин коду, які є настільки неефективними, що можуть сильно перешкоджати роботі програми або її виводу. Однак я вважаю, що це питання для переповнення стека, коли він звертається за допомогою до конкретних функцій, методів, викликів об'єктів тощо.

Моє запитання

Склавши (на мою думку) змішані C та C ++ там, де, можливо, я не повинен. Чи слід шукати, щоб переписати все це на C ++ (маючи на увазі, я маю на увазі реалізувати більше C ++ об’єктів та методів, де, можливо, я зашифрував щось у стилі C, яке можна скоротити за допомогою новіших методів C ++) або видалити використання об'єктів стримерних рядків та повернути це все "назад" до коду С? Чи є тут правильний підхід? Я розгублений і мені потрібні певні вказівки, як зберегти цю програму «Добре» в очах мас, щоб вони її використали та отримали від неї користь.

Код - оновлення

Ось посилання на код. Це приблизно 40% коментарів, я коментую майже кожен рядок, поки не відчуваю себе більш вільним. У примірнику, з яким я посилався, я видалив майже всі коментарі. Я сподіваюся, що це не ускладнює читання. Я сподіваюся, що нікому не потрібно це повністю розуміти. Якщо я зробив фатальні недоліки в дизайні, я сподіваюся, що їх слід легко визначити. Я також повинен зазначити, я пишу пару робочих столів та ноутбуків Ubuntu. Я не збираюся надсилати код до інших операційних систем.


3
Я розбіг CLI для вас. CLI також може посилатися на загальну мовну інфраструктуру, яка не має особливого сенсу з точки зору С.
Роберт Харві

Ви можете переписати його на FORTRAN.Я ніколи не чув про OOF.
ott--

2
Я б не переймався над тим, що люди не користуються вашим програмним забезпеченням, якщо це не "красиво". 99% ваших користувачів навіть не дивляться на код або не дбають про те, як він написаний, поки він виконує те, що їм потрібно зробити. Однак важливо, щоб код був послідовним тощо, щоб допомогти вам у підтримці його довгостроково.
Евікатос

1
Чому б вам не зробити свою річ вільним програмним забезпеченням (наприклад, за ліцензією GPLv3 ), з кодом, наприклад, github . У вашому коді відсутній LICENSEфайл. Ви можете отримати цікаві відгуки.
Базиль Старинкевич

Відповіді:


13

Почнемо спочатку: змішаний код C і C ++ досить поширений. Отже, ви у великому клубі для початку. У нас величезна кількість кодових баз С у дикій природі. Але з очевидних причин багато програмістів відмовляються писати хоча б нові речі на C, маючи доступ до C ++ у тому ж компіляторі, нові модулі починають писати саме так - спочатку просто залишаючи існуючі частини в спокої.

Потім з часом деякі існуючі файли перекомпілюються як C ++, а деякі мости можна видалити ... Але це може зайняти дуже багато часу.

Ви дещо випереджаєте, ваша повна система зараз є C ++, просто більшість із них написана "C-style". І ви бачите, що поєднання стилів є проблемою, чого не слід: C ++ - це багатопарадигмічна мова, яка підтримує багато стилів, і дозволяє їм співіснувати назавжди. Насправді це головна сила, що ви не змушені до єдиного стилю. Таку, яка була б неоптимальною тут і там, з деякою удачею не скрізь.

Переробити базу коду - це гарна ідея, ЯКЩО вона порушена. Або якщо це на шляху розвитку. Але якщо воно працює (у первісному розумінні слова), будь ласка, дотримуйтесь найосновнішого принципу інженерії: якщо він не зламався, не виправляйте. Залиште холодні частини в спокої, докладіть зусиль там, де це розраховує. На деталі, які погані, небезпечні - або в нових функціях, і просто на рефакторних деталях, щоб зробити їх ліжком.

Якщо ви шукаєте загальні речі для вирішення, ось що варто вилучити з кодової бази C:

  • всі функції str * та char [] - замініть їх на клас рядків
  • якщо ви використовуєте sprintf, створіть версію, яка повертає рядок з результатом або помістить його в рядок, і замініть використання. (Якщо ви ніколи не турбуєтесь потоками, зробіть собі прихильність і просто пропустіть їх, якщо вам це не подобається; gcc забезпечує ідеальну безпеку типу з вікна для перевірки форматів, просто додайте належний атрибут.
  • найменші та безкоштовні - НЕ з новими та видаленими, а векторними, списками, картами та іншими колекціонерами
  • решта управління пам’яттю (після попередніх двох пунктів вона повинна бути досить рідкісною, покривати розумними покажчиками або впроваджувати ваші спеціальні колекції
  • замініть все інше використання ресурсів (FILE *, mutex, блокування тощо), щоб використовувати обгортки або класи RAII

Коли ви закінчите з цим, ви підходите до точки, коли база даних коду може бути безпечною для винятків, тому ви можете скидати футбол із зворотним кодом, використовуючи винятки та рідкісну спробу / ловити лише у функціях високого рівня.

Крім цього, просто запишіть новий код в якийсь здоровий C ++, і якщо в них народжуються деякі класи, які є гарною заміною в існуючому коді, підберіть їх.

Я не згадав про синтаксичні речі, явно використовую refs замість покажчиків у всьому новому коді, але заміна старих частин C тільки для цієї зміни не є корисною. Ви повинні вирішити, усунути все, що можна, і використовувати варіанти C ++ у функціях обгортки до кінця. І що дуже важливо, додайте const, де це можливо. Вони переплітаються з більш ранніми кулями. І закріпіть ваші макроси та замініть те, що ви можете зробити, на enum, вбудовану функцію чи шаблон.

Я пропоную прочитати стандарти кодування Саттера / Александреску, якщо вони ще не зроблені, і уважно їх дотримуватися.


Дуже дякую за вхідний Балог, всі звукові поради в моїх очах, і я погоджуюся з усім цим. Я буду дивитись повільно змінювати розділ коду за розділом, який я думаю, визначивши пріоритет з робочим кодом.
jwbensley

7

Коротше кажучи: ти не підеш у пекло, нічого поганого не станеться, але і ти не виграєш жодних змагань з краси.

Хоча цілком можна використовувати C ++ як "кращий C", ви викидаєте багато переваг C ++, але оскільки ви не обмежуєте себе ваніллю C, ви не отримуєте жодних переваг С (простота, портативність) прозорість, сумісність). Іншими словами: C ++ жертвує деякими якостями C для отримання інших - наприклад, прозорість C, де завжди можна чітко бачити, де і коли відбувається розподіл пам'яті, торгується для більш потужних абстракцій C ++.

Оскільки, схоже, ваш код зараз працює нормально, мабуть, не надто добре його переписувати лише заради нього: зберігайте його таким, яким він є зараз, і змінюйте його на більш ідіоматичний C ++, як ви йдете, по одній штуці за раз, щоразу, коли ви працюєте над будь-якою частиною. І майте на увазі засвоєні таким чином уроки для свого наступного проекту, найбільше цього: C і C ++ - це не одна і та ж мова, і вам краще зробити вибір наперед, ніж вирішити перейти на C ++ на півдорозі вашого проекту .


Дякую tdammers за пораду. Я згоден з тим, що ви говорите, і я приймаю це на борт, дякую!
jwbensley

4

C ++ - дуже складна мова, в тому сенсі, що вона має багато особливостей. Це також мова, яка підтримує кілька парадигм, тобто вона дозволяє повністю писати процедурний код без використання будь-яких об'єктів.

Оскільки C ++ настільки складний, ви часто бачите, як люди використовують у своєму коді обмежений набір його функцій. Початківці часто просто використовують потік вводу-виводу, рядкові об'єкти та new / delete замість malloc / free, а можливо, посилання замість покажчиків. Дізнавшись про об'єктно-орієнтовані функції, ви можете почати писати у стилі, який називається "C з класами". Зрештою, дізнавшись більше про C ++, ви починаєте використовувати шаблони, RAII , STL , смарт-покажчики тощо.

Я намагаюся зазначити, що вивчення C ++ - це процес, який вимагає часу. Так, зараз ваш код, ймовірно, виглядає так, що його написав програміст C, який намагався написати C ++. А оскільки ви просто навчаєтесь, це абсолютно нормально. Однак це мене змушує засумувати, коли я бачу подібну річ у виробничому коді, написаному досвідченими програмістами, які повинні краще знати.

Пам'ятайте лише, що хороший програміст Fortran може написати хороший код Fortran будь-якою мовою. :)


2

Здається, ви рекапітулюєте саме той шлях, який пройшли багато старих програмістів на C, переходячи до C ++. Ви використовуєте це як "кращий C". Двадцять років тому це мало певний сенс, але на даний момент у С ++ існує стільки потужніших конструкцій (як Бібліотека стандартних шаблонів), що зараз це рідко має сенс. Як мінімум, ви, ймовірно, повинні зробити наступне, щоб уникнути аневризми програмістів C ++ під час перегляду вашого коду:

  • Використовувати потоки замість? printfсім'я.
  • Використовуйте newзамість malloc.
  • Використовуйте класи контейнерів STL для всіх структур даних.
  • Використовуйте std::stringзамість того, char*де це можливо
  • Розуміти та використовувати RAII

Якщо ваша програма коротка (і ~ 900 рядків здається короткою), я особисто не думаю, що намагатися створити надійний набір класів не потрібно або навіть корисний.


Дякую за пораду Стівену, Так, у мене була однакова ідея про заняття, і я думаю, що я можу прибрати код в один акуратний єдиний файл. Дякую!
jwbensley

2

У прийнятій відповіді згадуються лише плюси перетворення C на ідіоматичний C ++, як ніби код C ++ був би в якомусь абсолютному сенсі кращим, ніж код C. Я погоджуюся з іншими відповідями, що, мабуть, не потрібно робити різких змін змішаного коду, якщо він не порушений.

Чи стійке змішування C і C ++, залежить від способу перемішування. Тонкі помилки можуть бути введені, наприклад, коли у С-коді підвищуються винятки (що не є виключенням безпечним), викликаючи витоки пам'яті або пошкодження даних. Більш безпечний і досить поширений спосіб - обернути бібліотеки C або інтерфейси в класах, або використовувати їх в іншому виді ізоляції в проекті C ++.

Перш ніж ви вирішите переписати весь проект на ідіоматичний C ++ (або C), ви повинні пам’ятати, що багато змін, представлених в інших відповідях, можуть зробити вашу програму повільнішою або викликати інші небажані ефекти. Наприклад:

  • зміна виділених стеком C-рядків на std :: рядки може спричинити непотрібні виділення купи
  • зміна необроблених покажчиків на деякі типи загального вказівника (наприклад, std :: shared_ptr) викликає накладні витрати через підрахунок посилань, внутрішніх функцій віртуального члена та безпеки потоку
  • стандартні бібліотечні потоки повільніше, ніж аналоги C
  • необережне використання класів RAII, як контейнери, може спричинити зайві операції, особливо коли семантику переміщення C ++ 11 не можна використовувати
  • шаблони можуть викликати більш тривалий час компіляції, незрозумілі помилки компіляції, розмивання коду та проблеми переносимості між компіляторами
  • писати безпечний виняток код важко, що робить введення тонких помилок під час конверсії просто
  • хитріше використовувати C ++ код із спільної бібліотеки, ніж звичайний C
  • C ++ не підтримується настільки широко, як, наприклад, C89

На закінчення, перетворення змішаного коду C та C ++ у ідіоматичний C ++ слід розглядати як компроміс, який має набагато більше, ніж просто час впровадження та розширення вашої панелі інструментів, здавалося б, зручними функціями. Тому дуже важко дати відповідь на загальний випадок, окрім "це залежить".


"стандартні потоки бібліотеки повільніше, ніж аналоги C": Мабуть, звичайно важче для інтернаціоналізації, ніж printf у будь-якому випадку.
Дедуплікатор

1

Погано змішувати C і C ++ погано. Вони є різними мовами, і насправді слід розглядати як такі. Виберіть те, що вам найбільше подобається, і спробуйте написати ідіоматичний код цією мовою.

Якщо C ++ заощаджує вам багато коду, тоді дотримуйтесь C ++ і перепишіть C частини. Я сумніваюся, різниця в продуктивності навіть буде помітна, якщо саме це вас хвилює.


Тож у мене є одна бібліотека, яку я хочу використовувати, яка написана на C, і у мене є інша бібліотека, яку я хочу використовувати, яка написана на C ++ ... Перезапис коду, який працює просто чудово, просто створює непотрібну роботу.
gnasher729

1

Я весь час повертаюсь назад і назад між C і C ++ і я дивний тип, який вважає за краще працювати таким чином. Я віддаю перевагу C ++ для речей вищого рівня, тоді як я використовую C як тупий інструмент, який дозволяє мені рентгенівські типи даних і обробляти їх як біти та байти з, скажімо, memcpyщо мені здається корисним для реалізації структур низького рівня даних та розподільників пам'яті . Якщо ви виявляєте, що працюєте на рівні сирих бітів і байтів, то справді багата система типу C ++ взагалі нічого не допомагає, і мені часто простіше написати такий код на C, де я сміливо можу припустити, що я можу трактуйте будь-який тип даних C як лише біти та байти.

Головне, що потрібно пам’ятати, - це те, де ці дві мови добре не з’єднуються.

1. Функція змінного струму ніколи не повинна викликати функцію C ++, яка може throw. З огляду на те, скільки місць ваш щоденний вид C ++-коду може неявно ставати винятком, це означає, що ваші C-функції взагалі не повинні викликати функції C ++. Інакше ваш код C не зможе звільнити ресурси, які він виділяє під час розмотування стека, оскільки у нього немає практичного способу зловити виняток C ++. Якщо в кінцевому підсумку вимагається, щоб ваш код C викликав функцію C ++ як зворотний дзвінок, наприклад, тоді C ++ сторона повинна переконатися, що будь-яке виняток, з яким він стикається, виявиться перед поверненням до коду С.

2. Якщо ви пишете код C, який розглядає типи даних як просто необроблені біти та байти, бульдозуючи над типовою системою (це фактично моя головна причина використання C в першу чергу), ви ніколи не хочете використовувати такий код проти даних C ++ типи, які можуть мати конструктори копій та деструктори та віртуальні покажчики та речі такого роду, які потрібно дотримуватися. Отже, як правило, це повинен бути код C, використовуючи код цього типу C, а не C ++ код, що використовує код цього типу C.

Якщо ви хочете, щоб ваш код C ++ використовував такий код C, то зазвичай ви хочете використовувати його як деталі реалізації класу, що гарантує, що дані, які він зберігає в загальній структурі даних C, є типом даних, який тривіально побудувати. і знищити. На щастя, є такі ознаки типу, які ви можете використовувати для перевірки статичного затвердження, переконуючись, що типи, які ви зберігаєте в загальній структурі даних C, мають тривіальні деструктори та конструктори і залишатимуться таким чином.

Чи слід шукати, щоб переписати все це на C ++ (маючи на увазі, я маю на увазі реалізувати більше C ++ об’єктів та методів, де, можливо, я зашифрував щось у стилі C, яке можна скоротити за допомогою новіших методів C ++) або видалити використання об'єктів стримерних рядків та повернути це все "назад" до коду С?

На мою думку, вам не потрібно турбуватися, якщо ви дотримуєтесь наведених вище правил і переконайтеся, що ваш код добре перевірений на написаних вами тестах. Частина, яку, можливо, варто вдосконалити - це код, який ви написали на C ++ до цього часу, але це не означає, що вам потрібно портувати код, який строго на базі C базується на C ++.

З кодом, який ви написали на C ++ і компілюється як код C ++, ви повинні бути обережними. Використання C-подібного кодування в C ++ викликає проблеми. Наприклад, якщо ви виділяєте та звільняєте ресурси вручну, велика ймовірність, що ваш код не є безпечним для винятків, оскільки ваш код може зіткнутися з винятком, в який момент ви неявно вийдете з функції, перш ніж ви зможете freeвикористовувати ресурс. У C ++, RAII і такі речі, як розумні покажчики, не є пухнастою зручністю, оскільки вони можуть з'явитися, якщо ви подивитесь лише на звичайні контури виконання, не враховуючи виняткових шляхів. Вони часто є принциповою необхідністю просто та ефективно писати правильний безпечний для винятків код, який не просто почне просочуватися в усьому місці при зустрічі з винятком.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.