Мне всегда кажется, что я пишу код на C, который в основном является объектно-ориентированным, так что, скажем, у меня есть исходный файл или что-то подобное, я бы создал структуру, а затем передал указатель на эту структуру функциям (методам), принадлежащим этой структуре:
struct foo {
int x;
};
struct foo* createFoo(); // mallocs foo
void destroyFoo(struct foo* foo); // frees foo and its things
Это плохая практика? Как мне научиться писать С "правильным образом".
Ответы:
Нет, это неплохая практика, ее даже поощряют, хотя можно даже использовать такие соглашения, как
struct foo *foo_new();
иvoid foo_free(struct foo *foo);
Конечно, как говорится в комментарии, делайте это только в случае необходимости. Нет смысла использовать конструктор для
int
.Префикс
foo_
является соглашением, которому следует множество библиотек, поскольку он защищает от конфликтов с именами других библиотек. Другие функции часто имеют соглашение об использованииfoo_<function>(struct foo *foo, <parameters>);
. Это позволяет вамstruct foo
быть непрозрачным типом.Посмотрите документацию libcurl для соглашения, особенно с "подпространствами имен", так что вызов функции
curl_multi_*
выглядит неправильно с первого взгляда, когда первый параметр был возвращенcurl_easy_init()
.Есть еще более общие подходы, см. Объектно-ориентированное программирование с ANSI-C
источник
std::string
, не могли бы выfoo::create
? Я не использую C. Может быть, это только в C ++?Это не плохо, это отлично. Объектно-ориентированное программирование - это хорошо (если вы не увлечены, вы можете получить слишком много хорошего). C - не самый подходящий язык для ООП, но это не должно помешать вам извлечь из него максимум пользы.
источник
Это не плохо. Он рекомендует использовать RAII, который предотвращает многие ошибки (утечки памяти, использование неинициализированных переменных, использование после освобождения и т. Д., Которые могут вызвать проблемы безопасности).
Итак, если вы хотите скомпилировать свой код только с помощью GCC или Clang (а не с помощью компилятора MS), вы можете использовать
cleanup
атрибут, который будет должным образом разрушать ваши объекты. Если вы объявите свой объект так:Затем
my_str_destructor(ptr)
будет запущен, когда ptr выходит из области видимости. Просто имейте в виду, что его нельзя использовать с аргументами функции .Кроме того, не забывайте использовать
my_str_
в именах ваших методов, потому чтоC
у них нет пространств имен, и легко столкнуться с некоторыми другими именами функций.источник
#define
ваши typenames,__attribute__((cleanup(my_str_destructor)))
вы получите их как неявные во всей#define
области видимости (они будут добавлены во все объявления переменных).#define
тип d или его массивы). Короче говоря, это не стандарт C, и вы платите с большой гибкостью в использовании.__attribute__((cleanup()))
значительной степени квази-стандарт. Тем не менее, б) и в) все еще стоят ...Такой код может иметь много преимуществ, но, к сожалению, стандарт C не был написан для его облегчения. Исторически компиляторы предлагали эффективные поведенческие гарантии, выходившие за рамки требований Стандарта, которые позволяли писать такой код более четко, чем это возможно в Стандарте С, но в последнее время компиляторы начали отзывать такие гарантии во имя оптимизации.
Наиболее примечательно, что многие компиляторы C исторически гарантировали (путем разработки, если не документации), что, если два типа структуры содержат одинаковую начальную последовательность, указатель на любой тип может использоваться для доступа к членам этой общей последовательности, даже если типы не связаны, и далее, что в целях установления общей начальной последовательности все указатели на структуры эквивалентны. Код, который использует такое поведение, может быть намного чище и более безопасным по типу, чем код, который этого не делает, но, к сожалению, даже несмотря на то, что Стандарт требует, чтобы структуры, разделяющие общую начальную последовательность, были изложены одинаково, он запрещает коду фактически использовать указатель одного типа для доступа к начальной последовательности другого.
Следовательно, если вы хотите написать объектно-ориентированный код на C, вы должны решить (и должны принять это решение на раннем этапе) либо перепрыгнуть через множество обручей, чтобы соблюдать правила типа указателя C, и быть готовым к современные компиляторы генерируют бессмысленный код, если он проскальзывает, даже если более старые компиляторы сгенерировали бы код, который работает как задумано, или иначе задокументировали бы требование, что код будет использоваться только с компиляторами, которые настроены для поддержки поведения указателя старого стиля (например, используя "-fno-строгий псевдоним") Некоторые люди считают "-fno-строгий псевдоним" злом, но я хотел бы предположить, что более полезно думать о "-fno-строгом псевдониме" C как о языке, который предлагает большую семантическую силу для некоторых целей, чем «стандартный» C,но за счет оптимизаций, которые могут быть важны для некоторых других целей.
Например, на традиционных компиляторах исторические компиляторы интерпретируют следующий код:
выполняя следующие шаги по порядку: увеличивать первый член
*p
, дополнять младший бит первого члена*t
, затем уменьшать первый член*p
и дополнять младший бит первого члена*t
. Современные компиляторы переставлять последовательность операций в моде , какой код , который будет более эффективным , еслиp
иt
идентификации различных объектов, но изменить поведение , если они этого не делают.Этот пример, конечно, намеренно придуман, и на практике код, который использует указатель одного типа для доступа к элементам, являющимся частью общей начальной последовательности другого типа, обычно будет работать, но, к сожалению, так как нет способа узнать, когда такой код может потерпеть неудачу его невозможно безопасно использовать, за исключением отключения анализа псевдонимов на основе типов.
Несколько менее надуманный пример мог бы возникнуть, если бы кто-то захотел написать функцию для выполнения чего-то вроде замены двух указателей на произвольные типы. В подавляющем большинстве компиляторов "1990-х C" это можно сделать с помощью:
В Стандарте C, однако, нужно использовать:
Если
*p2
он хранится в выделенном хранилище, а временный указатель не хранится в выделенном хранилище, эффективным типом*p2
станет тип временного указателя и код, который пытается использовать в*p2
качестве любого типа, который не соответствует временному указателю. Тип вызовет неопределенное поведение. Крайне маловероятно, что компилятор заметит такую вещь, но поскольку современная философия компилятора требует, чтобы программисты избегали Undefined Behavior любой ценой, я не могу придумать никаких других безопасных способов написания вышеуказанного кода без использования выделенного хранилища ,источник
foo_function(foo*, ...)
псевдо-OO в C - это просто особый стиль API, который выглядит как классы, но его более правильно назвать модульным программированием с абстрактными типами данных.Следующим шагом является скрытие объявления структуры. Вы помещаете это в файл .h:
А затем в файле .c:
источник