пятница, февраля 26, 2010

C++0x: Делегирующие конструкторы (Delegating Constructors)

Еще одна приятная фича C++0x, которой лично мне очень не хватало - делегирующие конструкторы. Дальше я кратко изложу вот этот документ: Delegating Constructors (revision 3)[pdf].

Бывает так, что классу нужно несколько конструкторов, и тогда бывает удобно из одного конструктора вызвать другой. А сделать этого нельзя. Сейчас это решается функциями инициализации. Это несколько неинтуитивный способ и у него есть недостатки.
Пример кода с функцией инициализации.

class X {
void CommonInit();
Y y_;
Z z_;
public:
X();
X( int );
X( W );
};

X::X() : y_(42), z_(3.14) { CommonInit(); }
X::X( int i ) : y_(i), z_(3.14) { CommonInit(); }
X::X( W e ) : y_(53), z_( e ) { CommonInit(); }


Недостатки этого метода:
1. Несмотря на то, что тела у этих конструкторов совпадают, слить их в один не получится.
2. Невозможно делегировать инициализацию переменных.

Начинающие программисты часто не знают, что вызвать один конструтор из другого нельзя и пишут что-то вроде такого:
class X {
int i_;
public:
X();
X( int );
};
X::X() { DoSomethingObservableToThisObject(); }
X::X( int i ) : i_(i) { X(); }


Этот код компилируется, но делает совсем не то, что хотел программист.

Чего будет в С++0x: один конструктор, он называется "делегирующий конструктор", сможет вызывать другой, "целевой конструктор".

Пример кода:
class X {
int i_;
public:
X( int i ) : i_(i) { }
X() : X(42) { } // i_ == 42
};


Сильно лучше, чем было, но я нашла один существенный минус. Если делегирующие конструкторы образуют цикл, то результатом будет undefined behavior. Диагностику новый Стандарт не требует.

Также см. Стандарт C++0x, 12.6.2.

9 коммент.:

somereal комментирует...

спасибо, побольше бы про новый стандарт да на русском :)

Alena комментирует...

2Tor:

спасибо, побольше бы про новый стандарт да на русском :)

Евгений Зуев обещал, что скоро все будет :-).

Анонимный комментирует...

Почему бы просто не разрешить вызывать конструкторы друг из друга?

Alena комментирует...

2denizzzka:

Почему бы просто не разрешить вызывать конструкторы друг из друга?

Потому что сейчас конструкция вида
X::X( int i ) : i_(i) { X(); }
легальна, она означает создание локального объекта внутри конструктора. И если придать этому выражению другой смысл, то это может сломать чей-нибудь старый код.

Kirill V. Lyadvinsky комментирует...

Когда нужно сделать что-то общее для нескольких конструкторов, то на ум приходит перекинуть общее в конструктор базового класса. Вызов конструкторов друг из друга усложняет программу.

Например, сколько раз деструктор вызовется, если эксепшен в вызываемом конструкторе. Можно ли будет поймать эксепшен конструктора в другом конструкторе? Будет ли вызван дополнительный конструктор базового класса A(int), если в наследнике пришем B() : B(int) {} ?

Alena комментирует...

2jia3ep

Когда нужно сделать что-то общее для нескольких конструкторов, то на ум приходит перекинуть общее в конструктор базового класса. Вызов конструкторов друг из друга усложняет программу.

У этого решения много минусов.
Допустим, нет базового класса. Вводить еще один класс для этого?
Также не удастся инициализировать переменные класса-наследника.

Например, сколько раз деструктор вызовется, если эксепшен в вызываемом конструкторе. Можно ли будет поймать эксепшен конструктора в другом конструкторе? Будет ли вызван дополнительный конструктор базового класса A(int), если в наследнике пришем B() : B(int) {} ?

A(int) вызовется, если он есть в списке инициализации B(int).
Про исключения не знаю, Стандарт смотреть лень.

Анонимный комментирует...

Например, сколько раз деструктор вызовется, если эксепшен в вызываемом конструкторе.

Ни разу - но это же проблема для любого недостроенного конструктора.

Будет ли вызван дополнительный конструктор базового класса A(int), если в наследнике пришем B() : B(int) {} ?
Кстати, да. Хороший вопрос.
Наверно можно будет зациклить вызов конструкторов ->
B() : B(int) {}
B(int) : B() {}

Unknown комментирует...

Прошу прощения за некропостинг.
На сколько я знаю (хоть и никогда не использовал в рабочем коде), сейчас валиден такой синтаксис:
class A
{
private:
int x_;
int y_;

public:
A():x_(3)
{
}

A(int y):y_(y)
{
this->A::A();
}
};

Alena комментирует...

2Alex:

На сколько я знаю (хоть и никогда не использовал в рабочем коде), сейчас валиден такой синтаксис:

По Стандарту не валиден. Потому что
12.1/1: Constructors do not have names

12.1/2: Because constructors do not have names, they are never found during name
lookup

Т.е. даже компиляться не должно. Однако VC++2008 у меня это скомпилял без проблем. Возможно даже это будет работать как ожидалось. Но использовать такой код я бы не стала...

Еще подробное обсуждение подобного синтаксиса есть здесь.