Можно ли предотвратить пропуск элементов инициализации агрегата? - PullRequest
43 голосов
/ 10 февраля 2020

У меня есть структура со многими членами одного типа, например,

struct VariablePointers {
   VariablePtr active;
   VariablePtr wasactive;
   VariablePtr filename;
};

Проблема в том, что если я забуду инициализировать один из членов структуры (например, wasactive), например, так:

VariablePointers{activePtr, filename}

Компилятор не будет жаловаться на это, но у меня будет один объект, который частично инициализирован. Как я могу предотвратить подобные ошибки? Я мог бы добавить конструктор, но он дважды продублировал бы список переменных, поэтому мне пришлось бы набирать все трижды!

Пожалуйста, добавьте также C ++ 11 ответов, если есть решение для C ++ 11 (в настоящее время я ограничен этой версией). Более поздние языковые стандарты тоже приветствуются!

Ответы [ 5 ]

42 голосов
/ 10 февраля 2020

Вот трюк, который вызывает ошибку компоновщика, если отсутствует необходимый инициализатор:

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

Использование:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

Результат:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

Предостережения:

  • До C ++ 14 это вообще не позволяет Foo быть агрегатом.
  • Технически это зависит от неопределенного поведения (нарушение ODR), но должно работать на любой вменяемой платформе.
22 голосов
/ 10 февраля 2020

Для clang и g cc вы можете скомпилировать с -Werror=missing-field-initializers, что превращает предупреждение об отсутствующих инициализаторах поля в ошибку. godbolt

Редактировать: Для MSV C, похоже, нет предупреждения даже на уровне /Wall, поэтому я не думаю, что это возможно предупредить об отсутствующих инициализаторах с этим компилятором. godbolt

7 голосов
/ 10 февраля 2020

Полагаю, это не элегантное и удобное решение, но должно работать также с C ++ 11 и выдавать ошибку времени компиляции (не времени компоновки).

Идея состоит в том, чтобы добавить в Ваш struct дополнительный член, в последней позиции, типа без инициализации по умолчанию (и который не может инициализироваться значением типа VariablePtr (или любым другим типом предшествующих значений)

К примеру

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

Таким образом, вы вынуждены добавить все элементы в ваш список инициализации агрегата, включая значение для явной инициализации последнего значения (целое число для sentinel, в примере), или вы получите "вызов" в удаленный конструктор ошибки 'bar'.

Итак

foo f1 {'a', 'b', 'c', 1};

компилируется, а

foo f2 {'a', 'b'};  // ERROR

нет.

К сожалению, также

foo f3 {'a', 'b', 'c'};  // ERROR

не компилируется.

- РЕДАКТИРОВАТЬ -

Как указано MSalters (спасибо), есть дефект (еще один дефект ) в моем исходном примере: значение bar можно инициализировать значением char (то есть преобразовать с int), поэтому работает следующая инициализация

foo f4 {'a', 'b', 'c', 'd'};

, и это может сильно сбить с толку.

Чтобы избежать этой проблемы, я добавил следующий удаленный конструктор шаблонов

 template <typename T> 
 bar (T const &) = delete;

, поэтому предыдущее объявление f4 дает ошибку компиляции, так как значение d перехватывается удаляемым конструктором шаблона

4 голосов
/ 10 февраля 2020

Для CppCoreCheck есть правило для точной проверки, если все элементы были инициализированы, и это можно превратить из предупреждения в ошибку - это обычно для всей программы, конечно.

Обновление:

Правило, которое вы хотите проверить, является частью типов безопасности Type.6:

Тип.6: Всегда инициализировать переменную-член: всегда инициализировать, возможно, используя конструкторы по умолчанию или инициализаторы членов по умолчанию.

2 голосов
/ 10 февраля 2020

Самый простой способ - не давать типу членов конструктор без аргументов:

struct B
{
    B(int x) {}
};
struct A
{
    B a;
    B b;
    B c;
};

int main() {

        // A a1{ 1, 2 }; // will not compile 
        A a1{ 1, 2, 3 }; // will compile 

Другой вариант: если ваши члены являются константами &, вы должны инициализировать их всех:

struct A {    const int& x;    const int& y;    const int& z; };

int main() {

//A a1{ 1,2 };  // will not compile 
A a2{ 1,2, 3 }; // compiles OK

Если вы можете жить с одним фиктивным константой и членом, вы можете комбинировать это с идеей @ max66 о страже.

struct end_of_init_list {};

struct A {
    int x;
    int y;
    int z;
    const end_of_init_list& dummy;
};

    int main() {

    //A a1{ 1,2 };  // will not compile
    //A a2{ 1,2, 3 }; // will not compile
    A a3{ 1,2, 3,end_of_init_list() }; // will compile

Из cppreference https://en.cppreference.com/w/cpp/language/aggregate_initialization

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

Другой вариант - взять сторожевую идею max66 и добавить немного синтаксиса c сахара для удобства чтения

struct init_list_guard
{
    struct ender {

    } static const end;
    init_list_guard() = delete;

    init_list_guard(ender e){ }
};

struct A
{
    char a;
    char b;
    char c;

    init_list_guard guard;
};

int main() {
   // A a1{ 1, 2 }; // will not compile 
   // A a2{ 1, init_list_guard::end }; // will not compile 
   A a3{ 1,2,3,init_list_guard::end }; // compiles OK
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...