Реализация и использование класса 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; // уничтожить, чтобы избежать утечки
// ресурсов
Как объясняется в правиле 13, полагаться на то, что объект уничтожит клиент, чревато ошибками, а в правиле 18 говорится, как можно модифицировать фабричную функцию для предотвращения наиболее частых ошибок в клиентской программе. Здесь же мы обсудим более серьезный недостаток приведенного выше кода: даже если клиент все делает правильно, мы не можем узнать, как будет вести себя программа.
Проблема в том, что getTimeKeeper возвращает указатель на объект производного класса (например, AtomicClock), а удалять этот объект нужно через указатель на базовый класс (то есть на TimeKeeper), при этом в базовом классе (TimeKeeper) объявлен невиртуальный деструктор. Это прямой путь к неприятностям, потому что в спецификации C++ постулируется, что когда объект производного класса уничтожается через указатель на базовый класс с невиртуальным деструктором, то результат не определен. Во время исполнения это обычно приводит к тому, что часть объекта, принадлежащая производному классу, никогда не будет уничтожена. Если getTimeKeeper возвращает указатель на объект класс AtomicClock, то часть объекта, принадлежащая AtomicClock (то есть данные-члены, объявленные в этом классе), вероятно, не будут уничтожены, так как не будет вызван деструктор AtomicClock. Те же члены, что относятся к базовому классу (то есть TimeKeeper), будут уничтожены, что приведет к появлению так называемых «частично разрушенных» объектов. Это верный путь к утечке ресурсов, повреждению структур данных и проведению изрядного времени в обществе отладчика.
Решить эту проблему легко: нужно объявить в базовом классе виртуальный деструктор. Тогда при удалении объектов производных классов будет происходить именно то, что нужно. Объект будет разрушен целиком, включая все его части:
class TimeKeeper {
public:
TimeKeeper;
virtual ~TimeKeeper;
...
};
TimeKeeper *ptk = get TimeKeeper;
...