Как убедиться, что ваш объект инициализируется нулями?

Обновление: я смотрю, есть ли способ обнуления всего класса сразу, потому что технически можно забыть добавить ' = 0 ' или же ' {} после каждого члена. В одном из комментариев упоминается, что явно заданный по умолчанию аргумент no-arg c-tor позволит инициализировать ноль во время инициализации значения формы MyClass c{};, Глядя на http://en.cppreference.com/w/cpp/language/value_initialization я не могу понять, какие из утверждений указывают это.

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

Конкретная проблема, с которой я сталкиваюсь, заключается в следующем: я хочу убедиться, что члены моих классов обнуляются как для (1) классов, которые объявляют c-tor по умолчанию, так и для (2) тех, которые этого не делают.

Для (2), инициализация с {} выполняет работу, потому что это синтаксис для инициализации значения, который переводится в инициализацию с нуля или в агрегированную инициализацию, если ваш класс является агрегатным - случай, когда члены, для которых не был предоставлен инициализатор (все!), инициализируются нулями.

Но для (1) я все еще не уверен, что будет лучшим подходом. Из всей информации, которую я собираю, я узнал, что если вы предоставляете c-tor по умолчанию (например, для установки некоторых членов на некоторые значения), вы должны явно обнулить оставшиеся члены, в противном случае синтаксис MyClass c = MyClass(); или C++11 MyClass c{}; не буду делать работу. Другими словами, инициализация значения в этом случае означает просто вызов вашего c-tor, и все (без обнуления).

Вы попадаете в ту же ситуацию, если объявляете c-tor, который принимает значения и устанавливает эти значения для подмножества членов, но вы хотите, чтобы другие члены были нулевыми: для этого не существует сокращений -I задумываюсь о 3 вариантах:

class MyClass
{
  int a;
  int b;
  int c;

  MyClass(int a)
  {
    this->a = a;
    // now b and c have indeterminate values, what to do? (before setting 'a')


    // option #1
    *this = MyClass{}; // we lost the ability to do this since it requires default c-tor which is inhibited by declaring this c-tor; even if we declare one (private), it needs to explicitly zero members one-by-one

    // option #2
    std::memset(this, 0, sizeof(*this)); // ugly C call, only works for PODs (which require, among other things, a default c-tor defaulted on first declaration)

    // option #3
    // don't declare this c-tor, but instead use the "named constructor idiom"/factory below
  }


  static MyClass create(int a)
  {
    MyClass obj{}; // will zero-initialize since there are no c-tors
    obj.a = a;
    return obj;
  }
};

Правильно ли мое рассуждение? Какой из 3 вариантов вы бы выбрали?

3 ответа

Решение

По моему скромному мнению, самый простой способ обеспечить нулевую инициализацию - добавить слой абстракции:

class MyClass
{
    struct
    {
        int a;
        int b;
        int c;
    } data{};
public:
    MyClass(int a) : data{a} {}
};

Перемещение элементов данных в структуру позволяет использовать инициализацию значения для выполнения нулевой инициализации. Конечно, теперь немного труднее получить доступ к этим элементам данных: data.a вместо просто a в MyClass, Конструктор по умолчанию для MyClass выполнит нулевую инициализацию data и все его члены из-за инициализации braced для data, Кроме того, мы можем использовать агрегатную инициализацию в конструкторах MyClass, который также инициализирует значение тех членов данных, которые не инициализированы явно.

Недостаток косвенного доступа к членам данных можно преодолеть с помощью наследования вместо агрегирования:

struct my_data
{
    int a;
    int b;
    int c;
};

class MyClass : private my_data
{
    MyClass() : my_data() {}
public:
    MyClass(int a) : MyClass() { this->a = a; }
};

Явно указав инициализатор базы my_data(), инициализация значения также вызывается, приводя к нулевой инициализации. Этот конструктор по умолчанию, вероятно, должен быть помечен как constexpr а также noexcept, Обратите внимание, что это больше не тривиально. Мы можем использовать инициализацию вместо присваивания, используя агрегат-инициализацию или конструкторы пересылки:

class MyClass : private my_data
{
public:
    MyClass(int a) : my_data{a} {}
};

Вы также можете написать шаблон обертки, которая обеспечивает нулевую инициализацию, думали выгода спорна в этом случае:

template<typename T>
struct zero_init_helper : public T
{
    zero_init_helper() : T() {}
};

struct my_data
{
    int a;
    int b;
    int c;
};

class MyClass : private zero_init_helper<my_data>
{
public:
    MyClass(int a) { this->a = a; }
};

Наличие предоставленного пользователем конструктора, zero_init_helper больше не является агрегатом, поэтому мы больше не можем использовать агрегатную инициализацию. Использовать инициализацию вместо присваивания в ctor of MyClass, мы должны добавить конструктор пересылки:

template<typename T>
struct zero_init_helper : public T
{
    zero_init_helper() : T() {}

    template<typename... Args>
    zero_init_helper(Args&&... args) : T{std::forward<Args>(args)...} {}
};

class MyClass : private zero_init_helper<my_data>
{
public:
    MyClass(int a) : zero_init_helper(a) {}
};

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


Также возможно реализовать ваш вариант №1 следующим образом:

class MyClass
{
  int a;
  int b;
  int c;

  MyClass() = default; // or public, if you like

public:
  MyClass(int a)
  {
    *this = MyClass{}; // the explicitly defaulted default ctor
                       // makes value-init use zero-init
    this->a = a;
  }
};

Как насчет делегирования конструктора?

class MyClass
{
  int a;
  int b;
  int c;

  MyClass() = default; // or public, if you like

public:
  MyClass(int a) : MyClass() // ctor delegation
  {
      this->a = a;
  }
};

[class.base.init] / 7 предполагает, что приведенный выше пример должен вызывать инициализацию значения, что приводит к инициализации нуля, поскольку у класса нетпредоставленных пользователем конструкторов по умолчанию [dcl.init]/8.2. Последние версии clang++, кажется, инициализируют объект с нуля, а последние версии g ++ - нет. Я сообщил об этом как об ошибке g ++ # 65816.

Как насчет использования инициализации в классе?

class Foo
{
    int _a{}; // zero-it
    int _b{}; // zero-it
public:
    Foo(int a): _a(a){} // over-rules the default in-class initialization
};

Вариант 4 и 5:

вариант 4:


MyClass(int a) :a(a), b(0), c(0)
  {
  }

вариант 5:


class MyClass
  {
      int a = 0;
      int b = 0;
      int c = 0;

      MyClass(int a) : a(a) {
      }
  }
Другие вопросы по тегам