Страницы

Поиск по вопросам

понедельник, 17 декабря 2018 г.

Скобки () {} при инициализации внутри класса

Подскажите пожалуйста, а есть ли разница между использованием круглых и фигурных скобок при инициализации конструктора внутри класса. Оба варианта работают корректно.
class A { public: char c; int d;
A(char ch) :c(ch) {} A(char ch, int i) :c{ch}, d{i} {} };
int main() { A first = A('a'); A second = A('b', 1);
cout << "First: " << first.c << endl; cout << "Second: " << second.c << ' ' << second.d << endl;
return 0; }


Ответ

Начнем с того, что имеются случаи, когда нельзя применять инициализацию одного из видов к членам данных класса.
Например, если у вас имеется член класса, который является агрегатом (структурой или массивом), то возможные виды инициализации ограничены.
Рассмотрим несколько примеров.
В данном примере внутри структуры A объявляется агрегатный член данных B, имеющий тип структуры. Тогда данное объявление конструктора будет некорректныым
struct A { A(int x) : b( x ) {}
struct B { int x; } b; };
int main() { A a( 10 ); }
Компилятор выдаст сообщение об ошибке, говорящее о том, что он не может преобразовать объект типа int в объект типа struct B. Однако если вы замените круглые скобки на фигурные,
struct A { A(int x) : b{ x } {}
struct B { int x; } b; };
int main() { A a( 10 ); }
то код будет успешно компилироваться, так как объект b будет инициализирован как агрегат.
Теперь если в конструкторе заменить тип у параметра с int на A::B, то ситуация изменится.
Данная программа будет успешно компилироваться
struct A { struct B;
A(B x) : b( x ) {}
struct B { int x; } b; };
int main() { A::B b = { 10 }; A a( b ); }
так как структуры, которые являются агрегатами, имеют конструктор копирования, создаваемый компилятором неявно.
Иная ситуация складывается, когда членом данных, который представляет собой агрегат, является массив. Как и в случае со структурой, данная программа не будет компилироваться
struct A { struct B;
A(int x) : b( x ) {}
int b[1]; };
int main() { int b = 10; A a( b ); }
так как нет преобразования из целочисленного типа в массив.
Однако если заменить параметр на массив, не важно, является он ссылкой или нет, как показано ниже
struct A { struct B;
A(int x[1]) : b( x ) {}
int b[1]; };
int main() { int b[1] = { 10 }; A a( b ); }
или
struct A { struct B;
A(int ( &x )[1]) : b( x ) {}
int b[1]; };
int main() { int b[1] = { 10 }; A a( b ); }
то программа не будет компилироваться, так как в первом случае нет преобразования из указателя в массив, а во втором случае, когда параметр объявлен как ссылка, массивы не имеют конструктора копирования.
Для инициализации члена данных, который является массивом, можно использовать следующую запись
struct A { struct B;
A(int x) : b{ x } {}
int b[1]; };
int main() { int b = 10; A a( b ); }
Данная программа успешно скомпилируется. Однако вы не можете запись с фигурными скобками заключить еще в круглые собкки, как показано ниже
struct A { struct B;
A(int x) : b({ x }) {}
int b[1]; };
int main() { int b = 10; A a( b ); }
Компилятор выдаст сообщение об ошибке, так как, опять-таки, для массивов нет конструктора копирования. Однако для структур такая запись инициализации будет успешно воспринята компилятором, так как структуры, как агрегаты, имеют неявно объявленный компилятором конструктор копирования.
struct A { A(int x) : b({ x }) {}
struct B { int x; } b; };
int main() { int b = 10; A a( b ); }
Данная программа успешно скомпилируется.
Для арифметических типов инициализация с фигурными скобками не разрешает "сужение" значения, то есть использовать в качестве инициализатора значение, которое потенциально не может разместиться в инициализируемом объекте. Поэтому следующая программа не будет компилироваться
struct A { A(int x) : b{ x } {} short b; };
int main() { int b = 10; A a( b ); }
Причиной ошибки будет то, что объект типа short не в состоянии разместить все значения объекта типа int, то есть будет иметь место "сужение" инициализирующего значения.
Однако если заменить фигурные скобки на круглые, то программа успешно скомпилируется
struct A { A(int x) : b( x ) {} short b; };
int main() { int b = 10; A a( b ); }
Когда инициализируемый член класса является определенный пользователем тип, то в дело вступают конструкторы, которые имеют параметр типа std::initializer_list
Например, в приведенной ниже программе, когда нет такого конструктора, можно инициализировать член класса как b( x ), или как b{ x }, или даже как b( { x } )
struct A { A(int x) : b({ x }) {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
Однако если в классе присутствует конструктор с параметром типа std::initializer list, то для инициализаций вида b{ x } и b( { x } ) будет вызван именно он. А для инициализации вида b( x ) будет вызван другой конструктор.
Например, для этой программы
struct A { A(int x) : b( x ) {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
будет выведено сообзение
B( int )
А для этой программы
struct A { A(int x) : b{ x } {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
будет выведено сообщение
B( std::initializer_list )
Вообще, эта тема достаточно обширная.
О некоторых причудах инициализации я написал на своем сайте в конце темы Шутка - ложь, но в ней намек, добрым молодцам урок.

Комментариев нет:

Отправить комментарий