Перехід від C ++ до C


83

Після кількох років кодування на C ++ мені нещодавно запропонували роботу з кодуванням на C, у вбудованому полі.

Покинувши в стороні питання, правильно чи неправильно відхиляти С ++ у вбудованому полі, в С ++ є деякі особливості / ідіоми, я б багато чого пропустив. Просто назвемо декілька:

  • Загальні, безпечні для типу структури даних (із використанням шаблонів).
  • RAII. Особливо у функціях з кількома точками повернення, наприклад, не потрібно пам’ятати про звільнення мьютексу в кожній точці повернення.
  • Деструктори загалом. Тобто ви один раз пишете d'tor для MyClass, тоді, якщо екземпляр MyClass є членом MyOtherClass, MyOtherClass не повинен явно деініціалізувати екземпляр MyClass - його d'tor викликається автоматично.
  • Простори імен.

Який ваш досвід переходу від C ++ до C?
Які замінники C ви знайшли для своїх улюблених функцій / ідіом C ++? Ви виявили якісь функції C, які б ви хотіли мати в C ++?


12
Мабуть, це вікі спільноти, якщо ви просто просите досвіду, а не поради.
Peter Alexander

6
Ви можете бути зацікавлені в Prog.SE .

11
@Peter: OP більше не може ставити CW, і це вимагало більше повторень, ніж він, коли це було ще можливо. Якщо ви вважаєте, що питання має бути зроблено спільно з wiki-спільнотою з будь-якої іншої причини, аніж для того, щоб дозволити більшій кількості користувачів редагувати повідомлення, що належать "спільноті", то ви дійсно хочете закрити питання.

4
Хіба це питання більше не підходить для programmers.se? Оскільки це, безумовно, "справжнє" питання, я кажу, що ми знову відкриваємо його і голосуємо за його переміщення. І це неможливо. В ПОРЯДКУ.
Лассе В. Карлсен,

21
Переїзд не відбудеться, поки програма SE не вийде з бета-версії, і в будь-якому випадку я думаю, що такий підхід до контролю якості не дає змоги померти. Це фрагментує спільноту, дратує користувачів, дублює питання та відповіді. Він створює хаос з неорганізованої інформації, яка раніше була доступною та доступною для перегляду на одному веб-сайті "програміста". Крім того, саме такі запитання, що мають величезні погляди та неймовірне підтримання голосу, змушують мене злитися між п’ятьма хлопцями та спільнотою в цілому.
Стефано Боріні

Відповіді:


68

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

Код, який у C ++ виглядав так:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);

перетворюється на:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);

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

Одного разу (ще в 1995 році) я спробував написати багато C для багатопроцесорної програми обробки даних. Вигляд, коли кожен процесор має власну пам’ять і програму. Постачальник постачав компілятором компілятор C (якийсь похідний HighC), їх бібліотеки були закритими, тому я не міг використовувати GCC для побудови, а їх API були розроблені з урахуванням того, що ваші програми в першу чергу будуть ініціалізацією / процесом / термінантний різновид, тому міжпроцесорний зв’язок був у кращому випадку елементарним.

Я отримав приблизно місяць, перш ніж відмовитись, знайшов копію cfront і зламав її у файли make-файлів, щоб я міг користуватися C ++. Cfront навіть не підтримував шаблони, але код С ++ був набагато, набагато зрозумілішим.

Загальні, безпечні для типу структури даних (із використанням шаблонів).

Найближче до шаблонів - це оголошення файлу заголовка з великою кількістю коду, який виглядає так:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }

потім витягніть його з чимось на зразок

#define TYPE Packet
#include "Queue.h"
#undef TYPE

Зверніть увагу, що це не буде працювати для складних типів (наприклад, без черг unsigned char), якщо ви не зробите typedefперший.

О, і пам’ятайте, якщо цей код насправді ніде не використовується, то ви навіть не знаєте, чи він синтаксично правильний.

EDIT: Ще одне: вам потрібно буде вручну керувати створенням екземпляра коду. Якщо ваш код "шаблону" не всі вбудовані функції, то вам доведеться ввести певний контроль, щоб переконатися, що все запускається лише один раз, щоб ваш компонувальник не виплюнув купу помилок "декількох екземплярів Foo" .

Для цього вам доведеться помістити невбудований матеріал у розділ «реалізація» у вашому заголовковому файлі:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif

А потім, в одному місці у всьому коді на варіант шаблону , потрібно:

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE

Крім того, цей розділ реалізації повинен знаходитись поза межами стандартної #ifndef/ #define/ #endifектенії, оскільки ви можете включити файл заголовка шаблону в інший файл заголовка, але пізніше потрібно створити екземпляр у .cфайлі.

Так, це стає негарно швидко. Ось чому більшість програмістів на С навіть не намагаються.

RAII.

Особливо у функціях з кількома точками повернення, наприклад, не потрібно пам’ятати про звільнення мьютексу в кожній точці повернення.

Ну, забудьте свій гарний код і звикніть, що всі ваші точки повернення (крім кінця функції) становлять gotos:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
    TYPE * result;
    Mutex_Lock(this->lock);
    if(this->head == this->tail)
    {
        result = 0;
        goto Queue_##TYPE##_Top_exit:;
    }

    /* Figure out `result` for real, then fall through to... */

Queue_##TYPE##_Top_exit:
    Mutex_Lock(this->lock);
    return result;
}

Деструктори загалом.

Тобто ви один раз пишете d'tor для MyClass, тоді, якщо екземпляр MyClass є членом MyOtherClass, MyOtherClass не повинен явно деініціалізувати екземпляр MyClass - його d'tor викликається автоматично.

Побудова об'єкта повинна явно оброблятися однаково.

Простори імен.

Це насправді просто, щоб виправити: просто прикріпіть префікс до кожного символу. Це основна причина роздуття джерела, про яку я вже говорив раніше (оскільки класи - це неявні простори імен). Люди С живуть цим, ну, вічно, і, мабуть, не побачать, у чому велика справа.

YMMV


59
Звичайно, ти ненавидиш С, якщо намагаєшся змусити його бути С ++. Я сумніваюся, що C ++ буде виглядати чудово, якби ви спробували нав'язати йому функції з $ more_expressive_language. Не критика на ваш пост, просто спостереження :-)

Щодо техніки goto-вместо-RAII: це не кошмар обслуговування? тобто, коли ви додаєте шлях до коду, який потребує очищення, або навіть просто змінює порядок речей усередині функції, вам слід пам’ятати, що потрібно перейти до міток goto в кінці та змінити їх також. Я хотів би бачити техніку, яка якимось чином реєструє код очищення поряд із тим, що потрібно очистити.
george

2
@george: Я ненавиджу це говорити, але більшість вбудованих кодів C, які я бачив, досить погані за стандартами C. Наприклад, я зараз працюю з At91 at91lib, і він вимагає, щоб ви написали файл "board.h", який більшість коду втягує як залежність. (Для демонстраційної дошки цей заголовок має 792 рядки.) Крім того, функція "LowLevelInit ()", яку ви повинні налаштувати для своєї дошки, майже повністю реєструє доступ, з такими рядками, якAT91C_BASE_PMC->PMC_MOR = (0x37 << 16) | BOARD_OSCOUNT | AT91C_CKGR_MOSCRCEN | AT91C_CKGR_MOSCXTEN | AT91C_CKGR_MOSCSEL;
Mike DeSimone

1
О, і нічого там говорить вам , що BOARD_OSCOUNT(це значення тайм - ауту для очікування для годин з вимикачем ,? Ясно, да) не є на самому справі #defineв board.h. У цій самій функції також є багато скопійованого та вставленого коду спінового циклу, який слід було перетворити на дворядковий #define(і, коли я це зробив, він зберег кілька байт коду і зробив функція більш читабельна, роблячи набори регістрів та спінові цикли більш чіткими). Однією з головних причин використання C є те, що він дозволяє вам мікрокерувати та оптимізувати все, але більшість кодів, які я бачив, не турбують.
Mike DeSimone

5
Погодьтеся з @Mads. Немає причин переглядати все це для функцій, які вам насправді не потрібні. Я вважаю, що мені подобається стиль, подібний до бібліотеки GTK. Визначте свої "класи" як структури, а потім створіть послідовні методи, такі як my_class_new (), а потім перенесіть їх у "методи": my_class_do_this (my_class_instance)
Макс

17

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

  • призначені ініціалізатори (з часом поєднані з макросами) роблять ініціалізацію простих класів настільки ж безболісною, як і конструктори
  • складені літерали для тимчасових змінних
  • for-scope змінна може допомогти вам здійснити управління ресурсами, пов'язаними з обсягом , зокрема, забезпечити unlockмутекси або freeмасиви, навіть за попередніх повертань функції
  • __VA_ARGS__ макроси можна використовувати, щоб мати аргументи за замовчуванням для функцій і виконувати розгортання коду
  • inline функції та макроси, які добре поєднуються для заміни (начебто) перевантажених функцій

2
@Mike: для якої частини зокрема? Якщо ви перейдете за посиланням, яке я дав для forсфери застосування, ви потрапите на P99, де ви також можете оглянути приклади та описи інших частин.
Jens Gustedt

1
@Mike: Ось.
george

@george: Дякую! @Jens: Приклади інших чотирьох. Я відстав на своєму C; востаннє я чув, що вони додали масиви з автоматичним розподілом (тобто стеком) розміру часу виконання (наприклад void DoSomething(unsigned char* buf, size_t bufSize) { unsigned char temp[bufSize]; ... }) та ініціалізацію структури за іменами полів (наприклад struct Foo bar = { .field1 = 5, .field2 = 10 };), останні, які я хотів би бачити в C ++, особливо з об'єктами, що не є POD (наприклад UART uart[2] = { UART(0x378), UART(0x278) };) .
Mike DeSimone

@Mike: так, є масиви змінної довжини (VLA), але вони можуть бути дещо небезпечними для використання через потенційний потік stackoverflow. Друге, що ви описуєте, - це саме "призначений ініціалізатор", тож ви йдете із власним прикладом ;-) Для інших ви знайдете інформацію за посиланням P99 вище, якщо натиснете "пов'язані сторінки".
Jens Gustedt

8

Для C.
немає нічого подібного до STL. Є доступні бібліотеки, які забезпечують подібну функціональність, але вона вже не вбудована.

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


це правда. чи міг хтось детальніше пояснити, які бібліотеки класу контейнерів слід використовувати для C? Або відповідь "напишіть один"?
Сандіп

@Sandeep: Для початківців ця відповідь правильна лише щодо контейнерів, які не перебувають у стандартній бібліотеці. Окрім відсутності еквівалента STL (найкраща частина C ++), стандартна бібліотека C значно перевершує. POSIX містить tsearch, lsearch, hsearch та bsearch на додаток до qsort, який знаходиться у libc. Glib - це остаточний "прискорення" C, подивіться, він упакований смаколиками (в комплекті є контейнери). library.gnome.org/devel/glib/stable . Glib також інтегрується з Gtk +, комбінацією, яка перевищує Boost і Qt. Існує також libapr, популярний для матеріалів xplatform, таких як Subversion та Apache.
Matt Joiner

Я не можу знайти жодної бібліотеки c, яка могла би конкурувати зі stl, їх важче використовувати, важче підтримувати, продуктивність не є конкурентом stl, коли вони хочуть зберегти c libs такими ж загальними, як stl. обмеження c та причини, чому ми не можемо мати щось на зразок stl у бібліотеці c, оскільки c просто не має можливості розвивати щось на зразок stl.
StereoMatching

8

Різниця між C та C ++ полягає у передбачуваності поведінки коду.

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

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

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


4
Кожного разу, коли я турбуюся про те, що насправді робить код, я додаю -sпрапор, gccщоб отримати дамп збірки, шукаю функцію, що викликає занепокоєння, і починаю читати. Це чудовий спосіб вивчити будь-які примхи складеної мови.
Mike DeSimone

2
Це також даремна втрата часу, оскільки бачити, що збірка, створена на C ++, було б як читати Perl. Браво, що все одно шукаю.
Matt Joiner

7

У своєму напрямі роботи, який, до речі, є вбудованим, я постійно перемикаюся між C і C ++.

Коли я в C, я сумую за C ++:

  • шаблони (включаючи, але не обмежуючись контейнерами STL). Я використовую їх для таких речей, як спеціальні лічильники, пули буферів тощо (створив власну бібліотеку шаблонів класів та шаблонів функцій, які використовую в різних вбудованих проектах)

  • дуже потужна стандартна бібліотека

  • деструктори, які, звичайно, роблять можливим RAII (мутекси, відключення переривань, трасування тощо)

  • специфікатори доступу для кращого встановлення того, хто може використовувати (не бачити) що

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

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

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

Зважаючи на вибір, я віддав би перевагу використанню C ++ у проекті, але лише за умови, що команда досить твердо володіє мовою. Також, звичайно, припускаючи, що це не проект 8K μC, де я так чи інакше фактично пишу "С".


2
Ця "крива довіри C ++" мене трохи турбує. Те, як це написано та коментарі, означає, що C ++ є безнадійним, втраченою справою чи чим завгодно. Мені чогось не вистачає?
Mike DeSimone

Поглибіться, ми побачимось через кілька років. Більшість хороших програмістів виходять з іншого боку з кислим смаком.
Matt Joiner

3

Пара спостережень

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

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

2

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

uint32_t 
ScoreList::FindHighScore(
  uint32_t p_PlayerId)
{
  MutexLock lock(m_Lock); 

  uint32_t highScore = 0; 
  for(int i = 0; i < m_Players.Size(); i++)
  {
    Player& player = m_Players[i]; 
    if(player.m_Score > highScore)
      highScore = player.m_Score; 
  }

  return highScore; 
}

У C це виглядає так:

uint32_t 
ScoreList_getHighScore(
  ScoreList* p_ScoreList)
{
  uint32_t highScore = 0; 

  Mutex_Lock(p_ScoreList->m_Lock); 

  for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
  {
    Player* player = p_ScoreList->m_Players[i]; 
    if(player->m_Score > highScore)
      highScore = player->m_Score; 
  }

  Mutex_UnLock(p_ScoreList->m_Lock);

  return highScore; 
}

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

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


У C ви не можете зробити це "for (int i = 0".
Віктор

6
Віктор, це дійсно c99 (або компіляція c за допомогою компілятора c ++ для деяких питань набору тексту).
Роман А. Тайчер

Я вважаю, що не можу довіряти безпеці. Отже, ваш мьютекс має обсяг. Тепер ви не знаєте, чому його розблокування має блукати виняток. Ви навіть не знаєте, коли його буде розблоковано, будь-яка частина вашого коду може вирішити, що його вистачить, і кинути. Ці додаткові неявні "запобіжні заходи" можуть маскувати помилки.
Matt Joiner,

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

0

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

Так, ті ж міркування можна застосувати і до C, але, на щастя, у C не так багато підводних каменів, які можуть застрелити собі ногу. C ++, з іншого боку, ви повинні знати, коли не використовувати певні функції в c ++.

Загалом, мені подобається c ++. Я використовую це на рівні сервісу O / S, драйвера, коду управління тощо. Але якщо у вашої команди недостатньо досвіду з цим, це буде складним завданням.

Я мав досвід роботи з обома. Коли решта команди не була готова до цього, це було цілковитою катастрофою. З іншого боку, це був хороший досвід.


0

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


С ++ насправді не є надмножиною С; дуже легко написати код C, який не вдасться скомпілювати на компіляторі C ++. З іншого боку, Objective-C був (є?) Суворою надмножиною C.
ex nihilo

@exnihilo Супернабір конвенцій тут полягає у визначенні того, що C ++ має більше можливостей. Це також покращує синтаксис та семантику та зменшує ймовірність помилок. Є код, який не компілюється на C ++, але може на C, наприклад const int a; Це призводить до помилки в C ++, оскільки необхідно ініціалізувати константу під час оголошення, тоді як в C концепція буде скомпільована. Тож надмножина не є жорстким і швидким правилом, як у математиці (A⊂B), швидше це наближення того, що C ++ надає додаткові функції, такі як об’єктно-орієнтована концепція.
kaynat liaqat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.