Я полагаю, что я смешал код C и C ++, когда не должен был; Это проблема и как исправить?

10

Предпосылки / Сценарий

Я начал писать CLI-приложение исключительно на C (моя первая настоящая программа на C или C ++, которая не была «Hello World» или ее разновидностью). Примерно на полпути я работал со «строками» пользовательского ввода (массивами символов) и обнаружил объект C ++ string streamer. Я видел, что я могу сохранить код, используя их, поэтому я использовал их через приложение. Это означает, что я изменил расширение файла на .cpp и теперь скомпилирую приложение g++вместо gcc. Исходя из этого, я бы сказал, что приложение теперь технически является приложением C ++ (хотя более 90% кода написано на том, что я бы назвал C, так как между этими двумя языками существует много перекрестных связей, учитывая мой ограниченный опыт два). Это один файл .cpp длиной около 900 строк.

Важные факторы

Я хочу, чтобы программа была бесплатной (как в деньгах), свободно распространяемой и доступной для всех. Меня беспокоит то, что кто-то посмотрит на код и подумает о чем-то таком:

О, посмотрите на кодирование, это ужасно, эта программа не может мне помочь

Когда потенциально это возможно! Другое дело, насколько эффективен код (это программа для тестирования Ethernet-подключения). Не должно быть частей кода, которые были бы настолько неэффективными, что могли бы серьезно снизить производительность приложения или его вывода. Тем не менее, я думаю, что это вопрос переполнения стека при обращении за помощью с конкретными функциями, методами, вызовами объектов и т. Д.

Мой вопрос

Смешивать (по моему мнению) C и C ++ там, где, возможно, я не должен. Стоит ли мне переписать все это на C ++ (под этим я подразумеваю реализацию большего количества объектов и методов C ++, где, возможно, я написал что-то в стиле C, которое можно сжать с использованием более новых методов C ++), или исключу использование объектов string streamer и принести все это "обратно" в C-код? Есть ли правильный подход здесь? Я заблудился и нуждаюсь в некотором руководстве о том, как сохранить это приложение «Добрым» в глазах масс, чтобы они использовали его и извлекали из него пользу.

Код - Обновление

Вот ссылка на код. Это около 40% комментариев, я комментирую почти каждую строку, пока не почувствую себя более свободно. В той копии, на которую я ссылался, я удалил почти все комментарии. Я надеюсь, что это не делает это слишком трудным для чтения. Однако я надеюсь, что никто не должен полностью понимать это. Если я сделал роковые недостатки дизайна, я надеюсь, что они должны быть легко идентифицируемыми. Я должен также упомянуть, я пишу пару настольных компьютеров и ноутбуков Ubuntu. Я не собираюсь переносить код на другие операционные системы.

jwbensley
источник
3
Я снял неоднозначность CLI для вас. CLI также может ссылаться на Common Language Infrastructure, которая не имеет особого смысла с точки зрения Си.
Роберт Харви
Вы могли бы переписать это на Фортране. Я никогда не слышал об OOF.
ot--
2
Я бы не стал слишком беспокоиться о людях, не использующих ваше программное обеспечение, если оно не «красивое». 99% ваших пользователей даже не будут смотреть на код или заботиться о том, как он написан, если он выполняет то, что им нужно для этого. Однако важно, чтобы код был согласованным и т. Д., Чтобы помочь вам поддерживать его в долгосрочной перспективе.
Evicatos
1
Почему бы вам не сделать свое свободное программное обеспечение (например, под лицензией GPLv3 ) с кодом, например, на github . В вашем коде отсутствует LICENSEфайл. Вы можете получить интересную обратную связь.
Василий Старынкевич

Ответы:

13

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

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

Вы немного впереди, ваша полная система теперь C ++, просто большая часть написана в "C-стиле". И вы видите смешивание стилей проблемой, чего вам не следует: C ++ - это мультипарадигмальный язык, поддерживающий множество стилей и позволяющий им сосуществовать навсегда. На самом деле это главная сила, что вас не заставляют придерживаться единого стиля. Тот, который был бы неоптимальным здесь и там, с некоторой удачей не везде.

Переработка кодовой базы - хорошая идея, ЕСЛИ она не работает. Или если это на пути развития. Но если это работает (в первоначальном смысле слова), пожалуйста, следуйте основному инженерному принципу: если он не сломан, не исправляйте его. Оставьте холодные части в покое, приложите свои усилия там, где это важно. На части, которые плохие, опасные - или в новых функциях, и просто детали рефакторинга, чтобы превратить их в кровать.

Если вы ищете общие вещи для решения, вот что стоит исключить из базы кода C:

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

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

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

Я не упоминал о синтаксисе, очевидно, использовал ссылки вместо указателей во всем новом коде, но замена старых частей C только для этого изменения не имеет смысла. Приводит, что вы должны обратиться, исключить все, что вы можете, и использовать варианты C ++ в функциях-оболочках для оставшейся части. И что очень важно, добавьте const, где это применимо. Они чередуются с более ранними пулями. И объедините свои макросы, и замените то, что вы можете сделать, на enum, встроенную функцию или шаблон.

Я предлагаю прочитать Саттер / Александреску Стандарты кодирования C ++, если это еще не сделано, и внимательно им следовать.

Балог Пал
источник
Большое спасибо за ввод Balog, все здравые советы в моих глазах, и я согласен со всем этим. Я постараюсь медленно менять раздел кода за разделом, как мне кажется, отдавая приоритет рабочему коду.
Jwbensley
7

Короче говоря: вы не попадете в ад, ничего плохого не случится, но вы также не выиграете ни одного конкурса красоты.

Хотя вполне возможно использовать C ++ в качестве «лучшего C», вы отбрасываете многие преимущества C ++, но поскольку вы не ограничиваетесь ванильным C, вы не получаете никаких преимуществ C (простота, переносимость). прозрачность, интероперабельность) тоже. Другими словами: C ++ жертвует некоторыми из качеств C, чтобы получить другие - например, прозрачность C, где вы всегда можете четко видеть, где и когда происходит выделение памяти, обменивается на более мощные абстракции C ++.

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

tdammers
источник
Спасибо tdammers за совет. Я согласен с тем, что вы говорите, и я беру это на борт, спасибо!
Jwbensley
4

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

Так как C ++ настолько сложен, вы часто видите, как люди используют ограниченный набор его функций в своем коде. Начинающие часто просто используют потоковый ввод-вывод, строковые объекты и new / delete вместо malloc / free и, возможно, ссылки вместо указателей. Когда вы узнаете об объектно-ориентированных функциях, вы можете начать писать в стиле «C с классами». В конце концов, когда вы узнаете больше о C ++, вы начнете использовать шаблоны, RAII , STL , умные указатели и т. Д.

Я пытаюсь подчеркнуть, что изучение C ++ - это процесс, который требует времени. Да, сейчас ваш код, вероятно, выглядит так, как будто он был написан программистом C, пытающимся написать C ++. И так как вы только учитесь, это совершенно нормально. Однако это заставляет меня съеживаться, когда я вижу подобные вещи в производственном коде, написанном опытными программистами, которые должны знать лучше.

Просто помните, хороший программист на Фортране может написать хороший код на Фортране на любом языке. :)

Дима
источник
2

Похоже, вы повторяете путь, который многие старые программисты на Си пошли, переходя на С ++. Вы используете это как «лучший 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), вы должны знать, что многие изменения, представленные в других ответах, могут замедлить вашу программу или вызвать другие нежелательные эффекты. Например:

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

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

crafn
источник
«Потоки стандартной библиотеки медленнее, чем аналоги C»: скорее всего, конечно, труднее интернационализировать, чем printf в любом случае.
Дедупликатор
1

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

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

Oleksi
источник
Итак, у меня есть одна библиотека, которую я хочу использовать, которая написана на C, и у меня есть другая библиотека, которую я хочу использовать, которая написана на C ++ ... Переписать код, который работает просто отлично, просто создает ненужную работу.
gnasher729
1

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

Главное, что нужно иметь в виду, это то, что эти два языка плохо сочетаются.

1. Функция AC никогда не должна вызывать функцию C ++, которая может throw. Учитывая, сколько мест ваш ежедневный код C ++ может неявно запускать в исключение, это обычно означает, что ваши функции C не должны вызывать функции C ++ в целом. В противном случае ваш код C не сможет освободить ресурсы, которые он выделяет при разматывании стека, поскольку у него нет практического способа отловить исключение C ++. Если вы в конечном итоге требуете, чтобы ваш код C вызывал функцию C ++ в качестве обратного вызова, например, сторона C ++ должна убедиться, что любое исключение, с которым она сталкивается, перехватывается перед возвратом обратно в код C.

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

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

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

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

С кодом, который вы написали на C ++ и скомпилировали как код C ++, вы должны быть осторожны. Использование C-подобного кодирования в C ++ вызывает проблемы. Например, если вы выделяете и освобождаете ресурсы вручную, есть вероятность, что ваш код не является безопасным для исключений, так как ваш код может столкнуться с исключением, когда вы неявно выходите из функции, прежде чем вы сможете freeиспользовать ресурс. В C ++ RAII и такие вещи, как интеллектуальные указатели, не являются пугающим удобством, поскольку они могут появиться, если вы посмотрите только на нормальные пути выполнения без учета исключительных путей. Они часто являются фундаментальной необходимостью просто и эффективно написать правильный безопасный для исключений код, который не просто начнет просачиваться повсюду при обнаружении исключения.


источник