Перемещение с С++ на C

После нескольких лет программирования на С++ мне недавно предложили кодирование задания в C во встроенном поле.

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

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

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

Ответ 1

Работая над встроенным проектом, я пробовал работать во всех C один раз и просто не мог этого вынести. Это было так много, что было трудно читать что-либо. Кроме того, мне понравились оптимизированные для встроенных контейнеров, которые я написал, которые должны были стать менее безопасными и сложными для исправления блоков #define.

Код, который на С++ выглядел следующим образом:

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

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

У меня было около месяца, прежде чем я сдался, нашел копию cfront и взломал ее в make файлы, поэтому я может использовать С++. Cfront даже не поддерживал шаблоны, но код С++ был намного яснее.

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

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

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

RAII.

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

Хорошо, забудьте свой красивый код и привыкнете ко всем своим точкам возврата (кроме конца функции) goto s:

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 - его вызов вызывается автоматически.

Конструкция объекта должна быть явно обработана одинаково.

пространства имен.

Это на самом деле простая задача: просто прикрепите префикс к каждому символу. Это основная причина раздувания источника, о котором я говорил раньше (поскольку классы являются неявными пространствами имен). Люди C прожили это, ну, навсегда, и, вероятно, не увидит, что такое большая сделка.

YMMV

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

Разница между C и С++ - это предсказуемость поведения кода.

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

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

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

Ответ 5

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

Когда я нахожусь на C, я пропущу из С++:

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

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

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

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

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

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

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

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

Ответ 6

Пара наблюдений

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

Ответ 7

По большей части те же причины, что и для использования С++ или сочетание 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; 
}

Не мир разницы. Еще одна строка кода, но она имеет тенденцию складываться. Nomally вы стараетесь держать его в чистоте и тоните, но иногда вам нужно делать что-то более сложное. И в таких ситуациях вы цените количество строк. Еще одна строка - еще одна вещь, на которую нужно обратить внимание, когда вы пытаетесь понять, почему ваша широковещательная сеть внезапно прекращает доставку сообщений.

В любом случае, я считаю, что С++ позволяет мне делать более сложные вещи безопасным образом.

Ответ 8

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

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

В общем, мне нравится С++. Я использую это на уровне служб O/S, драйвер, код управления и т.д. Но если вашей команде не хватает опыта, это будет сложной задачей.

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