Переход с C++ на C


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

отложив в сторону вопрос о том, правильно или неправильно отклонять C++ во встроенном поле, есть некоторые функции/идиомы в C++, которые я бы пропустил много. Только чтобы назвать несколько:

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

каков ваш опыт перехода от C++ к C?
Какие заменители C вы нашли для своих любимых функций/идиом C++? Вы обнаружили какие-либо функции вы хотите На C++ было?

8 79

8 ответов:

работая над встроенным проектом, я попытался работать во всех 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-char). Оба будут генерировать один и тот же код, так что это не похоже на целевой процессор заботился!

однажды (еще в 1995 году) я попытался написать много C для многопроцессорной программы обработки данных. Где каждый процессор имеет собственную память и программа. Компилятор, поставляемый поставщиком, был компилятором C (своего рода производной HighC), их библиотеки были закрытый исходный код, поэтому я не мог использовать GCC для сборки, и их API были разработаны с учетом того, что ваши программы в первую очередь будут инициализировать/обрабатывать/завершать разнообразие, поэтому межпроцессорная связь была в лучшем случае рудиментарной.

я получил около месяца, прежде чем сдался, нашел копию cfront, и взломал его в makefiles, чтобы я мог использовать C++. Cfront даже не поддерживал шаблоны, но код на C++ был намного, намного яснее.

Generic, типобезопасные структуры данных (с использованием шаблонов).

самое близкое, что C имеет к шаблонам, - это объявить файл заголовка с большим количеством кода, который выглядит так:

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 litany, потому что вы можете включить файл заголовка шаблона в другой файл заголовка, но нужно создать экземпляр позже в .

да, опасно быстро. Который является, почему большинство с программисты даже не пытаются.

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

я перешел с C++ на C по другой причине (какая-то аллергическая реакция ;) и есть только несколько вещей, которые я пропустил, и некоторые вещи, которые я получил. Если вы придерживаетесь C99, если вы можете, есть конструкции, которые позволяют вам программировать довольно красиво и безопасно, в частности

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

ничего подобного STL не существует для C.
Есть доступные библиотеки, которые обеспечивают аналогичную функциональность, но она больше не встроена.

думаю, что это было бы одной из моих самых больших проблем... Зная, с помощью какого инструмента я мог бы решить проблему, но не имея инструментов, доступных на языке, который я должен использовать.

разница между C и c++ заключается в предсказуемости поведения кода.

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

предсказуемость в C дает вам лучший контроль над тем, что делает ваш код, но это также означает, что вам нужно делать больше вещей.

В C++ вы можете написать меньше кода, чтобы сделать то же самое, но (по крайней мере для я) у меня иногда возникают проблемы с тем, как объектный код выкладывается в памяти, и это ожидаемое поведение.

в моей работе-которая встроена, кстати-я постоянно переключаюсь между C и c++.

когда я нахожусь в C, я пропускаю из C++:

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

  • очень мощная стандартная библиотека

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

  • спецификаторы доступа, чтобы лучше применять, кто может использовать (не видеть) что

Я использую наследование на более крупных проектах, и встроенная поддержка C++для него намного чище и приятнее, чем c "hack" встраивания базового класса в качестве первого члена (не говоря уже об автоматическом вызове конструкторов, init. списки и т. д.) но перечисленные выше предметы-это те, по которым я скучаю больше всего.

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

с другой стороны, когда я возвращаюсь к проекту C со значительным количеством разработчиков, есть целые классы проблем C++, которые я привык объяснять людям, которые уходят. В основном проблемы из-за сложность C++, и люди, которые думают, что они знают, что происходит, но они действительно находятся в части "C с классами"C++ доверительная кривая.

учитывая выбор, я бы предпочел использовать C++ в проекте, но только если команда довольно солидна на языке. Также, конечно, предполагая, что это не проект 8K µC, где я эффективно пишу "C" в любом случае.

пару замечаний

  • Если вы не планируете использовать свой компилятор c++ для создания своего C (что возможно, если вы придерживаетесь хорошо определенного подмножества C++), вы скоро обнаружите, что ваш компилятор позволяет в C, что было бы ошибкой компиляции в C++.
  • больше никаких загадочных ошибок шаблона (yay!)
  • нет (язык поддерживается) объектно-ориентированное программирование

почти те же причины, по которым я использую 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++ труднее принять во встроенной среде, связана с отсутствием инженеров, которые понимают, как правильно использовать c++.

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

в целом, мне нравится c++. Я использую это на уровне служб O/S, Драйвер, код управления, etc. Но если ваша команда не имеет достаточного опыта в этом, это будет трудная задача.

У меня был опыт работы с обоими. Когда остальная команда не была готова к этому, это была катастрофа. С другой стороны, это был хороший опыт.