Каковы правила использования конструкторов и списков инициализаторов в фигурных скобках для инициализации классов и структур? - PullRequest
0 голосов
/ 31 декабря 2018

Я искал ответы на этот вопрос в Интернете, но пока не нашел удовлетворительного ответа.Мне было интересно, каковы все правила для инициализации объектов структур и типов классов, особенно когда дело доходит до конструкторов против фигурных списков инициализаторов.Отличаются ли правила для структур и классов?

Предположим, у нас есть класс или структура с именем Rectangle.

#include <iostream>
using namespace std;

class Rectangle {
  public:
    Rectangle() : x(5.0), y(6.0), width(7.0), height(8.0) {}

    void printMe()
    {
        cout << "The rectangle is located at (" << x << ',' << y << ") and is " << width << " x " << height << endl;
    }
    double x;
    double y;
    double width;
    double height;
};


int main()
{
    Rectangle r = {0.0, 0.0, 3.0, 4.0};
    r.printMe();

    Rectangle s;  // uninitialized!
    s.printMe();
}

Я пытаюсь инициализировать Rectangle r так, как вы бы это сделалиобычно делают это в C, используя простой старый скобочный список инициализатора.Однако g++ выдает следующую ошибку:

constructor_vs_initializer_list.cpp: In function ‘int main()’:
constructor_vs_initializer_list.cpp:21:38: error: could not convert ‘{0.0, 0.0, 3.0e+0, 4.0e+0}’ from ‘<brace-enclosed initializer list>’ to ‘Rectangle’
     Rectangle r = {0.0, 0.0, 3.0, 4.0};
                                      ^

Хммм .... Это не очень полезное сообщение об ошибке, как кажется на первый взгляд.Однако я думаю, что это как-то связано с конструктором, потому что, если я его удалю, код компилируется и запускается!Я думаю, это было бы парадоксом, так как список фигурных инициализаторов и конструктор конкурируют за инициализацию элементов данных, которые ему кажутся.

Однако, когда я сделал элементы данных private, после удаления конструктора, который является, то же сообщение об ошибке отображалось снова!

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

Rectangle() = default;
...
double x = 1.0;

Я не пишу, что их смешивание - это обязательно хороший код, простоэто код, и код, который должен быть хорошо понят по моему мнению.Спасибо.

Ответы [ 3 ]

0 голосов
/ 31 декабря 2018

Вот пример, демонстрирующий различия.Инициализация в C ++ довольно сложна.См .: https://blog.tartanllama.xyz/initialization-is-bonkers/.

Обычно лучше использовать инициализаторы членов по умолчанию или списки инициализации .Вы правильно делаете в своем конструкторе.Просто вызовите конструктор с прямой инициализацией списка или прямой инициализацией , чтобы избежать путаницы. Обычно , вы бы использовали инициализацию copy-list-initialization для инициализации агрегатов без предоставленных пользователем конструкторов.

#include <iostream>

struct A {
    int i;
};

struct B {
    B() = default;
    int i;
};

struct C {
    C();
    int i;
};

C::C() = default;

struct D {
    D(){};
    int i;
};

struct E : public D {
};

struct F {
    F(int i = 5) {}
    int i;
};

struct G {
    G() = delete;
    int i;
};

int main() {
    // g++ (v 8.2.1) provides good warnings about uninitialized values.
    // clang++ (v 7.0.1) does not.
    // Technically, they are initialized to 'indeterminate values', but it is
    // easier to refer to the member variables as uninitialized.

    {
        // All of the following are 'default initialized', meaning they are not even
        // zero-initialized. Members are UNINITIALIZED (Technically, they are
        // initialized to 'indeterminate' values.
        // Either nothing is done, or the default constructor is called (in
        // which nothing is done).
        A a;
        B b;
        C c;
        D d;
        E e;
        F f;

        std::cout << "a: " << a.i << std::endl;
        std::cout << "b: " << b.i << std::endl;
        std::cout << "c: " << c.i << std::endl;
        std::cout << "d: " << d.i << std::endl;
        std::cout << "e: " << e.i << std::endl;
        std::cout << "f: " << f.i << std::endl;
        std::cout << std::endl;
    } {
        // This is more complex, as these are all 'list initialized'.
        // Thank you, infinite wisdom of the C++ committee.

        A a{};
        // Direct list initialization -> aggregate initialization
        //  - A has no user-provided constructor and
        // thus is an aggregate, and agg. init. takes place.
        // This 'value initializes' all *MEMBERS* (unless a default member
        // initializer exists, which it does not here).
        // Value initialization of non-class types results in
        // zero-initialization. (member `i` is zero-initialized)

        A a2 = {};
        // same thing, but via copy list initialization

        A a3{{}};
        // recursive, initializes `i` with {}, which zero initializes `i`.

        A a4{0};
        // recursive, initializes `i` 0;
        // Could also do `A a4 = {0}`

        A a5{a};
        // direct intialization of `a5` with `a`.
        // Implicit copy constructor chosen by overload resolution.

        A a6{A{}};
        // post C++17, direct initializes a6 with a prvalue of type A, that is
        // aggregate initialized as above. NOT copy/move initialized, but
        // instead initialized via the "initializer expression itself".
        // I assume this means the value of a6 is directly set via as if it were
        // being aggregate initialized.

        B b{};
        // Same as A. `B() = default;` does NOT specify a user-provided
        // constructor

        C c{};
        // Because the first declaration of `C()` is not `C() = default;`,
        // this DOES have a user-provided constructor, and 'value initializaton'
        // is performed.
        // NOTE: this value intializes `C`, not the *MEMBERS* of `C`.
        // Because `C` is a normal class type, value initialization just calls
        // the default constructor, which does nothing, and leaves all members
        // uninitialized.

        D d{};
        // D is a class type that is list/direct initialization -> value
        // inititalizaton -> default initialization -> call constructor ->
        // members are left unitialized.

        E e{};
        // List initialization -> value initialization -> default initialization
        // -> calls implicitly defined default constructor -> Calls default
        // constructor of bases -> leaves E::D.i uninitialized

        F f{};
        // List/direct initialization -> value initialization -> calls default
        // constructor with default arguments -> leaves F.i uninitialized

        // G g{}; 
        // Fails to compile.
        // list initialization -> value initialization -> default initialization
        // -> deleted default constructor selected by overload resolution ->
        // fails to compile

        std::cout << "a: " << a.i << std::endl;
        std::cout << "a2: " << a2.i << std::endl;
        std::cout << "a3: " << a3.i << std::endl;
        std::cout << "a4: " << a4.i << std::endl;
        std::cout << "a5: " << a5.i << std::endl;
        std::cout << "a6: " << a6.i << std::endl;
        std::cout << "b: " << b.i << std::endl;
        std::cout << "c: " << c.i << std::endl;
        std::cout << "d: " << d.i << std::endl;
        std::cout << "e: " << e.i << std::endl;
        std::cout << "f: " << f.i << std::endl;
    }
}
0 голосов
/ 31 декабря 2018

Я не уверен, что вы подразумеваете под "списком инициализированных фигурных скобок", но вы не можете использовать фигурные скобки для инициализации подобного объекта.Вы можете использовать фигурные скобки для инициализации агрегатной структуры (где все члены данных являются общедоступными и нет конструкторов), но это на самом деле не лучшая практика.

Рекомендуется сделать элементы данных частными и использовать конструктор, подобный следующему:

#include <iostream>
using namespace std;

class Rectangle {
private:
    double _x = 0.0;
    double _y = 0.0;
    double _w = 0.0;
    double _h = 0.0;
public:
    Rectangle( const double & x, const double & y, const double & w, const double & h ) : _x(x), _y(y), _w(w), _h(h) {}
    Rectangle(){}

    void printMe() const
    {
        cout << "The rectangle is located at (" << _x << ',' << _y << ") and is " << _w << " x " << _h << endl;
    }
};


int main()
{
    Rectangle r(0.0, 0.0, 3.0, 4.0);
    r.printMe();

    Rectangle s;  // default value - all zeros
    s.printMe();
}

Теперь данные объекта правильно инкапсулированы, что предотвращает нежелательные побочные эффекты, и вы можете легкоинициализировать объект с помощью конструктора.Разница лишь в том, что вы будете использовать скобки вместо скобок.

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

Имейте в виду, что конструктор - это просто функция с синтаксисом, поддерживающая его роль конструктора.Применяются все основные правила функции.(Также обратите внимание, что я сделал константные ссылки на аргументы функции, чтобы избежать двойного копирования значений.)

0 голосов
/ 31 декабря 2018

В проекте стандарта CPP n4713 говорится об инициализации агрегата:

11.6.1 Агрегаты [dcl.init.aggr]
1 Агрегат - это массив иликласс с
(1.1) - без пользовательских, явных или унаследованных конструкторов,
(1.2) - без личных или защищенных нестатических членов-данных

В вашем случае выиметь предоставленный пользователем конструктор в первом случае и закрытые члены данных во втором случае, которые нарушают пункты (1.1) и (1.2) вышеупомянутых пунктов соответственно.

...