Существует возможность переместить ошибку с этапа компоновки на этап компиляции (это всегда полезно – лучше обнаружить ошибку как можно раньше), если объявить конструктор копирования и оператор присваивания закрытыми не в самом классе HomeForSale, а в его базовом классе, специально созданном для предотвращения копирования. Такой базовый класс очень прост:
class Uncopyable {
protected:
Uncopyable() {} // разрешить конструирование
~Uncopyable() {} // и уничтожение
// объектов производных классов
private:
Uncopyable(const Uncopyable&); // но предотвратить копирование
Uncopyable& operator=(const Uncopyable&);
};
Чтобы предотвратить копирование объектов HomeForSale, нужно лишь унаследовать его от Uncopyable:
class HomeForSale : private Uncopyable { // в этом класс больше нет ни
... // конструктора копирования, ни
} // оператора присваивания
Такое решение работает, потому что компилятор пытается генерировать конструктор копирования и оператор присваивания, если где-то – пусть даже в функции-члене или дружественной функции – производится попытка скопировать объект HomeForSale. Как объясняется в правиле 12, сгенерированные компилятором версии будут вызывать соответствующие функции из базового класса. Но это не получится, так как в базовом классе они объявлены закрытыми.
Реализация и использование класса Uncopyable сопряжена с некоторыми тонкостями. Например, наследование от Uncopyable не должно быть открытым (см. правила 32 и 39), а деструктор Uncopyable не должен быть виртуальным (см. правило 7). Поскольку Uncopyable не имеет данных-членов, то компилятор может прибегнуть к оптимизации пустых базовых классов, описанной в правиле 39, но коль скоро этот класс базовый, то возможно возникновение множественного наследования (см. правило 40). А множественное наследование в некоторых случаях не дает возможности провести оптимизацию пустых базовых классов (см. правило 39). Вообще говоря, вы можете игнорировать эти тонкости и просто использовать Uncopyable, как показано выше. Можете также воспользоваться версией из билиотеки Boost (см. правило 55). В ней этот класс называется noncopyable. Это хороший класс, но мне просто показалось, что его название немного, скажем так, неестественное.
• Чтобы отключить функциональность, автоматически предоставляемую компилятором, объявите соответствующую функцию-член закрытой и не включайте ее реализацию. Наследование базовому классу типа Uncopyable – один из способов сделать это.
Правило 7: Объявляйте деструкторы виртуальными в полиморфном базовом классе
Существует много способов отслеживать время, поэтому имеет смысл создать базовый класс TimeKeeper и производные от него классы, которые реализуют разные подходы к хронометражу:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper {…};
class WaterClock: public TimeKeeper {….};
class WristWatch: public TimeKeeper {…};
Многие клиенты захотят иметь доступ к данным о времени, не заботясь о деталях того, как они получаются, поэтому мы можем воспользоваться
TimeKeeper *getTimeKeeper(); // возвращает указатель на динамически
// выделенный объект класса,
// производного от TimeKeeper
В соответствии с соглашением о фабричных функциях объекты, возвращаемые getTimeKeeper, выделяются из кучи, поэтому для того, чтобы избежать утечек памяти и других ресурсов, важно, чтобы каждый полученный объект был рано или поздно уничтожен:
TomeKeeper *ptk = getTimeKeeper(); // получить динамически выделенный
// объект из иерархии TimeKeeper
... // использовать его
delete ptk; // уничтожить, чтобы избежать утечки
// ресурсов