};
Transaction::Transaction(const std::string loginfo)
{
...
logTransaction(loginfo); // теперь –
// невиртуальный
// вызов
}
class BuyTransaction : public Transaction {
public:
BuyTransaction(
: Transaction(createLogString(
{...} // для записи в протокол
... // конструктору базового
// класса
private:
static std::string createLogString(
}
Другими словами, если вы не можете вызывать виртуальные функции из конструктора базового класса, то можете компенсировать это передачей необходимой информации конструктору базового класса из конструктора производного.
В этом примере обратите внимание на применение закрытой статической функции createLogString в BuyTransaction. Использование вспомогательной функции для создания значения, передаваемого конструктору базового класса, часто удобнее (и лучше читается), чем отслеживание длинного списка инициализации членов для передачи базовому классу того, что ему нужно. Сделав эту функцию статической, мы избегаем опасности нечаянно сослаться на неинициализированные данные-члены класса BuyTransaction. Это важно, поскольку тот факт, что эти данные-члены еще не определены, и является основной причиной, почему нельзя вызывать виртуальные функции из конструкторов и деструкторов.
• Не вызывайте виртуальные функции во время работы конструкторов и деструкторов, потому что такие вызовы никогда не дойдут до производных классов, расположенных в иерархии наследования ниже того, который сейчас конструируется или уничтожается.
Правило 10: Операторы присваивания должны возвращать ссылку на *this
Одно из интересных свойств присваивания состоит в том, что такие операции можно выполнять последовательно:
int x,y,z;
x = y = z = 15; // цепочка присваиваний
Также интересно, что оператор присваивания правоассоциативен, поэтому приведенный выше пример присваивания интерпретируется следующим образом:
x = (y = (z = 15));
Здесь переменной z присваивается значение 15, затем результат присваивания (новое значение z) присваивается переменной y, после чего результат (новое значение y) присваивается переменной x.
Достигается это за счет того, что оператор присваивания возвращает ссылку на свой левый аргумент, и этому соглашению вы должны следовать при реализации операторов присваивания в своих классах:
class Widget {
public:
...
Widget operator=(const Widget rhs) // возвращаемый тип – ссылка
{ // на текущий класс
...
return *this; // вернуть объект из левой части
} // выражения
...
};
Это соглашение касается всех операторов присваивания, а не только стандартной формы, показанной выше. Следовательно:
class Widget {
public:
...
Widget operator+=(const Widget rhs) // соглашение распространяется на
{ // +=, -=, *=, и т. д.
...
return *this;
}
Widget operator=(int rhs) // это относится даже
{ // к параметрам разных типов
...
return *this;
}
...
};
Это всего лишь соглашение. Если программа его не придерживается, она тем не менее скомпилируется. Однако ему следуют все встроенные типы, как и все типы (см. правило 54) стандартной библиотеки (то есть string, vector, complex, tr1::shared_ptr и т. д.). Если у вас нет веской причины нарушать соглашение, не делайте этого.
• Пишите операторы присваивания так, чтобы они возвращали ссылку на *this.
Правило 11: В operator= осуществляйте проверку на присваивание самому себе
Присваивание самому себе возникает примерно в такой ситуации:
class Widget {...};
Widget w;
...
w = w; // присваивание себе
Код выглядит достаточно нелепо, однако он совершенно корректен, и в том, что программисты на такое способны, вы можете не сомневаться ни секунды.
Кроме того, присваивание самому себе не всегда так легко узнаваемо. Например:
a[i] = a[j]; // потенциальное присваивание себе
это присваивание себе, если i и j равны одному и тому же значению, и
*px = *py; // потенциальное присваивание себе