Компания, в которой я работаю, инициализирует все свои структуры данных с помощью функции инициализации следующим образом:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
InitializeFoo(Foo* const foo){
foo->a = x; //derived here based on other data
foo->b = y; //derived here based on other data
foo->c = z; //derived here based on other data
}
//initializing the structure
Foo foo;
InitializeFoo(&foo);
Я получил некоторый толчок назад, пытаясь инициализировать мои структуры следующим образом:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
Foo ConstructFoo(int a, int b, int c){
Foo foo;
foo.a = a; //part of parameter input (inputs derived outside of function)
foo.b = b; //part of parameter input (inputs derived outside of function)
foo.c = c; //part of parameter input (inputs derived outside of function)
return foo;
}
//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);
Есть ли преимущество одного над другим?
Что я должен сделать, и как бы я оправдал это как лучшую практику?
c
coding-style
constructors
initialization
Тревор Хикки
источник
источник
InitializeFoo()
- конструктор. Единственное отличие от конструктора C ++ состоит в том, чтоthis
указатель передается явно, а не неявно. Скомпилированный кодInitializeFoo()
и соответствующий C ++Foo::Foo()
абсолютно одинаковы.Ответы:
Во втором подходе у вас никогда не будет полуинициализированного Foo. Размещение всей конструкции в одном месте кажется более разумным и очевидным.
Но ... 1-й способ не так уж и плох, и часто используется во многих областях (даже обсуждается лучший способ внедрения зависимостей, либо инъекция свойств, как ваш 1-й способ, либо инжекция в конструктор, как 2-й) , Ни один не так.
Так что, если ни то, ни другое не верно, а остальная часть компании использует подход № 1, то вы должны соответствовать существующей кодовой базе и не пытаться испортить ее, вводя новый шаблон. Это действительно самый важный фактор в игре, играйте хорошо со своими новыми друзьями и не пытайтесь быть той особенной снежинкой, которая делает вещи по-другому.
источник
void SetupFoo(Foo *out, int a, int b, int c)
Foo
структуре? Первый подход также выполняет всю инициализацию в одном месте. (Или вы рассматриваете ип инициализируется -Foo
структуру , чтобы быть «наполовину инициализирован»?)Оба подхода объединяют код инициализации в один вызов функции. Все идет нормально.
Тем не менее, есть два вопроса со вторым подходом:
Второй фактически не создает результирующий объект, он инициализирует другой объект в стеке, который затем копируется в конечный объект. Вот почему я бы воспринял второй подход как немного худший. Вероятно, вы получили откат из-за этой посторонней копии.
Это еще хуже , когда вы черпаете класс
Derived
отFoo
(Структур в основном используется для ориентации объекта в C): При втором подходе, функцияConstructDerived()
будет называтьConstructFoo()
, скопировать полученный временныйFoo
объект в течение суперкласса слотDerived
объекта; закончить инициализациюDerived
объекта; только чтобы полученный объект был скопирован заново при возврате. Добавьте третий слой, и все это станет совершенно нелепым.При втором подходе
ConstructClass()
функции не имеют доступа к адресу строящегося объекта. Это делает невозможным связывание объектов во время конструирования, так как это необходимо, когда объект должен зарегистрировать себя в другом объекте для обратного вызова.Наконец, не все
structs
полноценные классы. Некоторыеstructs
эффективно просто связывают кучу переменных без каких-либо внутренних ограничений на значения этих переменных.typedef struct Point { int x, y; } Point;
будет хорошим примером этого. Для них использование функции инициализатора кажется излишним. В этих случаях может быть удобен составной литеральный синтаксис (это C99):или
источник
int x = 3;
не хранить строкуx
в двоичном файле. Адрес и точки наследования хороши; Предполагаемая неудача выбывания глупа.unsigned char
который позволял бы оптимизировать, для которого Строгое правило псевдонимов было бы недостаточно, одновременно делая ожидания программистов более ясными.В зависимости от содержимого структуры и конкретного используемого компилятора любой подход может быть быстрее. Типичным примером является то, что структуры, отвечающие определенным критериям, могут возвращаться в регистрах; для функций, возвращающих другие типы структур, вызывающая сторона должна выделять место для временной структуры где-то (обычно в стеке) и передавать свой адрес как «скрытый» параметр; в случаях, когда возврат функции сохраняется непосредственно в локальной переменной, адрес которой не содержится ни в каком внешнем коде, некоторые компиляторы могут передавать адрес этой переменной напрямую.
Если тип структуры удовлетворяет требованиям конкретной реализации, которые должны быть возвращены в регистрах (например, быть не больше одного машинного слова или заполнять ровно два машинных слова) с функцией возврата, структура может быть быстрее, чем передача адреса структуры, особенно поскольку предоставление адреса переменной внешнему коду, который может сохранить ее копию, может исключить некоторые полезные оптимизации. Если тип не удовлетворяет таким требованиям, сгенерированный код для функции, которая возвращает структуру, будет аналогичен коду для функции, которая принимает указатель назначения; вызывающий код, вероятно, будет быстрее для формы, которая принимает указатель, но эта форма теряет некоторые возможности оптимизации.
Жаль, что C не дает возможности сказать, что функции запрещено хранить копию переданного указателя (семантика аналогична ссылке на C ++), поскольку передача такого ограниченного указателя принесет прямые преимущества в производительности передачи указатель на существующий ранее объект, но в то же время избегайте семантических затрат, связанных с требованием, чтобы компилятор считал адрес переменной «открытым».
источник
Одним из аргументов в пользу стиля «выходной параметр» является то, что он позволяет функции возвращать код ошибки.
Рассматривая некоторый набор связанных структур, если инициализация может потерпеть неудачу для какой-либо из них, может быть целесообразно, чтобы все они использовали стиль out-параметра для согласованности.
источник
errno
.Я предполагаю, что вы сосредоточены на инициализации через выходной параметр, а не на инициализации через return, а не на несоответствии в том, как предоставляются аргументы конструкции.
Обратите внимание, что первый подход может позволить
Foo
быть непрозрачным (хотя и не так, как вы используете его в настоящее время), и это обычно желательно для долгосрочной ремонтопригодности. Например, вы можете рассмотреть функцию, которая выделяет непрозрачнуюFoo
структуру без ее инициализации. Или, возможно, вам нужно повторно инициализироватьFoo
структуру, которая ранее была инициализирована с другими значениями.источник