Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 74 75 76 [ 77 ] 78 79 80 ... 144


11.4. Создание и копирование объектов

Меня часто просили запретить некоторые операции в языке. Некоторые хотели оптимизировать реализацию классов, для чего необходимо снять разрешение на проведение над объектами классов таких операций, как копирование, наследование и распределение в стеке. В других случаях, когда с по.мощью объектов представлялись сущности реального мира, для обеспечения требуемой семантики не было нужды во всех операциях, которые поддерживает C-f-+.

Ответ на все подобные просьбы был найден во время работы над версией 2.0. Если вы хотите что-то запретить, сделайте соответствующую операцию закрытой функцией-членом (см. раздел 2.10).

11.4.1. Контроль допустимости копирования

Чтобы запретить копирование объектов класса X, достаточно сделать закры-ты.ми оператор присваивания и копирующий конструктор.

class X {

Х£с operator= (const Х&) ; присваивание X(const X&j; копирующий конструктор

. . . public: X(int) ; . . .

void f() {

X a(l); правильно: можно создавать объекты класса X

X b = а; ошибка: X::X(const Х&;) закрыт

b = а; ошибка: X::operator=(const Х&) закрыт

Конечно, внутри функций-членов класса X может копировать объекты данного класса, но в реальных ситуациях это допустимо, а иногда и необходимо. Не помню, кто первый додумался до этого решения; скорее всего, не я [Stroustrup, 1986, стр. 172].

Считаю неправильным, что операции копирования определены по умолчанию и во многих своих классах запрещаю копирование объектов. Однако присваивание по умолчанию и копирующие конструкторы перешли в С++ от С и эти возможности часто применяются.

11.4.2. Управление распределением памяти

С помощью объявления ряда операций закрытыми можно добиться и других эффектов. Например, если объявить закрытым деструктор, то будет запрещено размещение объектов в стеке и в глобальной памяти, а также случайное употребление delete:

class On free store {

~On free store(); закрытый деструктор - . .



public:

static void fгее(On free store* p) { delete p; } . . .

On free store globl; ошибка: деструктор закрыт

void f() {

On free store loc; ошибка: деструктор закрыт On free store* p = new On free store; правильно . . .

delete p; ошибка: деструктор закрыт On free store::fгее(p); правильно

Разумеется, подобный класс, как правило, будет использоваться вместе с хорошо оптимизированным распределителем свободной памяти или при наличии некоторой семантики, выигрывающей оттого, что объект находится в свободной памяти.

Противоположного эффекта - разрешения глобальных и локальных переменных при запрете на размещение объектов в свободной памяти - можно достичь с помощью необычного использования operator new ():

class No free store { class Dummy { };

void* operator new(size t,Dummy); ...

No free store glob2; правильно

void g() {

No free store loc; правильно

No free store* p = new No free store; ошибка:

No free store::operator new(size t) отсутствует

11.4.3. Управление наследованием

Закрытый деструктор предотвращает также и наследование. Например:

class D : public On free store {

. . .

D d; ошибка: нельзя вызвать закрытый деструктор базового класса

Это позволяет классу с закрытым деструктором стать логическим дополнением абстрактного класса. Наследовать классу On f ree store невоз.можно, поэтому



при вызовах виртуальных функций данного класса необязательно использовать механизм виртуализации. Однако я не думаю, что какой-нибудь из современных компиляторов выполняет такую опти.мизацию.

Впоследствии Эндрю Кениг обнаружил, что можно запретить наследование даже вне зависимости от того, в какой памяти может размещаться объект:

class Usable lock {

friend Usable; private:

Usable lock() {}

class Usable : public virtual Usable lock {

. . . public:

Usable();

Usable(char*);

.. .

Usable a;

class DD : public Usable { };

DD dd; ошибка: DD::DD() недоступен

Usable lock::Usable lock() - закрытый член

Этот пример основан на правиле, что производный класс может вызывать конструктор виртуального базового класса (явно или неявно).

Однако такие примеры - скорее, предмет для интеллектуальных дебатов, чем для применения на практике.

11.4.4. Почленное копирование

Первоначально присваивание и инициализация были по умолчанию определены как побитовое копирование. Это приводило к неприятностям, когда объект класса, в котором определен оператор присваивания, использовался в качестве члена класса, в котором такой оператор не был определен:

class X {/*...*/ Х& operator=(const Х&); };

struct Y { X а; };

void f{Y yl, Y у2) {

yl = У2;

Здесь у2 . a копируется в yl. a побитово. Очевидно, что это неправильно и является результатом недосмотра при проектировании оператора присваивания и копирующего конструктора. После ряда споров и упреков со стороны Эндрю



1 ... 74 75 76 [ 77 ] 78 79 80 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика