Инициализация объекта ко всем нулям
Зачастую инициализация структур данных требует, чтобы все члены были равны нулю. Даже при программировании на С++ может потребоваться интерфейс с внешним API, для которого это имеет место.
Существует ли какая-либо практическая разница между:
some_struct s;
memset(&s, 0, sizeof(s));
и просто
some_struct s = { 0 };
Есть ли люди, которые используют оба метода, с методом выбора, который более подходит для данного приложения? (Надеюсь, это понятно, что это применимо только к структурам POD, вы получите всевозможные хаосы, если в этой структуре был С++ std::string.)
Для меня, как в основном программиста на С++, который не использует memset, я никогда не уверен в сигнатуре функции, поэтому я считаю, что второй пример проще использовать в дополнение к тому, чтобы быть менее типичным, более компактным и может быть, даже более очевидным, поскольку в нем говорится, что "этот объект инициализирован равным нулю" прямо в объявлении, а не ждет следующей строки кода и видит "о, этот объект инициализирован нолем".
При создании классов и структур на С++ я склонен использовать списки инициализации; Мне любопытно думать о мыслях о двух инициализациях "C style" выше, а не о сравнении с тем, что доступно на С++, так как я подозреваю, что многие из нас взаимодействуют с библиотеками C, даже если мы сами кодируем в основном на С++.
Изменить: Нейл Баттерворт поставил этот вопрос в последующем, что я считаю интересным следствием этого вопроса.
Ответы
Ответ 1
memset
практически не подходит для этого. И да, есть практическая разница (см. Ниже).
В С++ не все может быть инициализировано литералом 0
(объектов типов перечислений быть не может), поэтому в С++ общая идиома
some_struct s = {};
а в C идиома
some_struct s = { 0 };
Обратите внимание, что в C = { 0 }
это то, что можно назвать универсальным инициализатором нуля. Он может использоваться с объектами практически любого типа, поскольку {}
-ключенные инициализаторы допускаются также со скалярными объектами
int x = { 0 }; /* legal in C (and in C++) */
что делает = { 0 }
полезным в универсальном не зависящем от типа C коде (например, независимые от типа макросы).
Недостатком инициализатора = { 0 }
в C89/90 и С++ является то, что он может использоваться только как часть объявления. (C99 исправил эту проблему, введя составные литералы. Аналогичная функциональность подходит и для С++.) По этой причине вы можете увидеть, что многие программисты используют memset
, чтобы что-то высчитать в середине кода C89/90 или С++. Тем не менее, я бы сказал, что правильный способ сделать все равно без memset
, а скорее с чем-то вроде
some_struct s;
...
{
const some_struct ZERO = { 0 };
s = ZERO;
}
...
то есть. введя "фиктивный" блок в середине кода, хотя он может выглядеть не слишком красивым с первого взгляда. Конечно, в С++ нет необходимости вводить блок.
Что касается практической разницы... Возможно, вы слышали, что некоторые люди говорят, что memset
будет давать те же результаты на практике, так как на практике физический все-нулевой бит-шаблон - это то, что используется для представления нулевых значений для всех типов, Однако это, как правило, неверно. Непосредственным примером, который продемонстрировал бы разницу в типичной реализации С++, является тип типа указателя на данные
struct S;
...
int S::*p = { 0 };
assert(p == NULL); // this assertion is guaranteed to hold
memset(&p, 0, sizeof p);
assert(p == NULL); // this assertion will normally fail
Это происходит из-за того, что типичная реализация обычно использует битовый шаблон all-one (0xFFFF...
) для представления нулевого указателя этого типа. Приведенный выше пример демонстрирует реальную практическую разницу между нулевым memset
и обычным инициализатором = { 0 }
.
Ответ 2
some_struct s = { 0 };
гарантированно работает; memset
опирается на детали реализации и лучше избегать.
Ответ 3
Если структура содержит указатели, значение всех битов 0, созданных с помощью memset
, может означать не то же самое, что присвоение ему 0
для него в коде C (или С++), то есть указатель NULL
.
(Возможно, это также относится к floats
и doubles
, но я никогда не встречался. Однако я не думаю, что стандарты гарантируют, что они станут равными нулю с помощью memset
.)
Изменить: С более прагматичной точки зрения, я бы сказал, что не следует использовать memset
, когда это возможно, чтобы избежать, поскольку это дополнительный вызов функции, более длинный для записи и (в моем мнение) менее ясно в намерении, чем = { 0 }
.
Ответ 4
В зависимости от оптимизации компилятора может быть некоторый порог, выше которого memset выполняется быстрее, но обычно это будет значительно выше обычного размера переменных на основе стека. Использование memset на объекте С++ с виртуальной таблицей, конечно, плохо.
Ответ 5
Я нашел хорошее решение:
template<typename T> void my_zero(T& e) {
static T dummy_zero_object;
e = dummy_zero_object;
}
my_zero(s);
Это правильное решение не только для основных типов и пользовательских типов, но и для нулевых инициализаций типов, для которых определено конструктор по умолчанию, но не инициализирует все переменные-члены, особенно классы, содержащие нетривиальные union
.
Ответ 6
Единственное практическое отличие состоит в том, что синтаксис ={0};
немного ясен, говоря "инициализировать это, чтобы он был пуст" (по крайней мере, мне кажется яснее).
Чисто теоретически существует несколько ситуаций, в которых memset
может потерпеть неудачу, но, насколько я знаю, они действительно такие: теоретические. OTOH, учитывая, что он уступает как теоретической, так и практической точке зрения, мне трудно понять, почему кто-то хотел бы использовать memset
для этой задачи.
Ответ 7
Мы надеемся, что это доступно только для структур POD; вы получите ошибку компилятора, если в этой структуре был С++ std::string.
Нет, нет. Если вы используете memset
на таких, в лучшем случае, вы просто потерпите крах, и в худшем случае вы получите какую-то тарабарщину. Путь = { }
можно использовать отлично для не-POD-структур, если они являются агрегатами. Способ = { }
- лучший способ взять на С++. Обратите внимание, что в С++ нет причины вставлять в это 0
, а также не рекомендуется, поскольку он резко сокращает случаи, в которых он может быть использован
struct A {
std::string a;
int b;
};
int main() {
A a = { 0 };
A a = { };
}
Первый не будет делать то, что вы хотите: он попытается создать std::string
из C-строки с указанием нулевого указателя на его конструктор. Второй, однако, делает то, что вы хотите: он создает пустую строку.
Ответ 8
Я никогда не понимал таинственной доброты, чтобы установить все на ноль, что даже если оно определено, кажется маловероятным. Поскольку это помечено как С++, правильным решением для инициализации является создание структуры или класса construtor.
Ответ 9
Я думаю, что инициализация говорит гораздо яснее, что вы на самом деле делаете. Вы инициализируете структуру. Когда новый стандарт выходит из этого способа инициализации, он будет еще более использоваться (инициализация контейнеров с {} - это что-то, на что можно рассчитывать). Способ memset немного более подвержен ошибкам, и не сообщает, что ясно, что вы делаете. Это может не отразиться на процессе программирования, но очень важно при работе в команде.
Для некоторых людей, работающих с С++, memset, malloc и co. являются довольно эзотерическими существами. Я встречался с несколькими.
Ответ 10
Лучший способ очистки структур - установить каждое поле отдельно:
struct MyStruct
{
std::string name;
int age;
double checking_account_balance;
void clear(void)
{
name.erase();
age = 0;
checking_account_balance = 0.0;
}
};
В приведенном выше примере используется метод clear
, чтобы установить все члены в известное состояние или значение. Методы memset
и std::fill
могут не работать из-за типов std::string
и double
. Более надежная программа очищает каждое поле отдельно.
Я предпочитаю иметь более надежную программу, чем тратить меньше времени на ввод.
Ответ 11
Функция bzero
- это еще одна опция.
#include <strings.h>
void bzero(void *s, size_t n);
Ответ 12
В C Я предпочитаю использовать {0,} эквивалентную memset().
Однако gcc предупреждает об этом использовании: (Подробнее здесь:
http://www.pixelbeat.org/programming/gcc/auto_init.html
В С++ они обычно эквивалентны, но, как всегда, с С++
есть угловые случаи, которые следует учитывать (отмеченные в других ответах).