Можно ли запретить наследование от класса во время компиляции? - PullRequest
11 голосов
/ 16 июня 2009

У меня есть класс значений в соответствии с описанием в «Стандартах кодирования C ++», пункт 32. Короче, это означает, что он обеспечивает семантику значений и не имеет никаких виртуальных методов.

Я не хочу, чтобы класс был производным от этого класса. Помимо других, одной из причин является то, что у него есть публичный не виртуальный деструктор. Но базовый класс должен иметь открытый и виртуальный деструктор или защищенный и не виртуальный.

Я не знаю возможности записать класс значений, чтобы его невозможно было извлечь из него. Я хочу запретить это во время компиляции. Есть ли какая-нибудь известная идиома, чтобы сделать это? Если нет, возможно, есть какие-то новые возможности в грядущем C ++ 0x? Или есть веские причины, по которым такой возможности нет?

Ответы [ 7 ]

25 голосов
/ 16 июня 2009

Бьярне Страуструп написал об этом здесь .


Соответствующий бит из ссылки:

Могу ли я остановить людей, выходящих из моего класса?

Да, но почему вы хотите? Есть два общих ответа:

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

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

Если существует реальная необходимость «ограничить» иерархию классов, чтобы избежать вызовов виртуальных функций, можно спросить, почему эти функции являются виртуальными. Я видел примеры, когда критичные к производительности функции были сделаны виртуальными без уважительной причины, просто потому, что «мы так обычно делаем».

Другой вариант этой проблемы, как предотвратить вывод по логическим причинам, имеет решение. К сожалению, это решение не красиво. Он основан на том факте, что самый производный класс в иерархии должен создавать виртуальную базу. Например:

class Usable;

class Usable_lock {
    friend class Usable;
private:
    Usable_lock() {}
    Usable_lock(const Usable_lock&) {}
};

class Usable : public virtual Usable_lock {
    // ...
public:
    Usable();
    Usable(char*);
    // ...
};

Usable a;

class DD : public Usable { };

DD dd;  // error: DD::DD() cannot access
        // Usable_lock::Usable_lock(): private  member

(из D & E sec 11.4.3).

8 голосов
/ 16 июня 2009

Если вы хотите разрешить создание класса только методом фабрики, у вас может быть приватный конструктор.

class underivable {
    underivable() { }
    underivable(const underivable&); // not implemented
    underivable& operator=(const underivable&); // not implemented
public:
    static underivable create() { return underivable(); }
};
6 голосов
/ 16 марта 2013

Даже если вопрос не помечен для C ++ 11, для людей, которые попадают сюда, следует отметить, что C ++ 11 поддерживает новый контекстный идентификатор final. Смотрите вики-страницу

4 голосов
/ 16 июня 2009

Внимательно посмотрите здесь .
Это действительно круто, но это взломать.
Интересно, почему stdlib не делает этого с собственными контейнерами?

2 голосов
/ 16 июня 2009

Ну, у меня была похожая проблема. Это опубликовано здесь на SO. Проблема была в другом. то есть разрешить получение только тех классов, которые вы разрешаете. Проверьте, решает ли это вашу проблему.

Это делается в время компиляции .

1 голос
/ 16 июня 2009

Я бы обычно достигал этого следующим образом:

// This class is *not* suitable for use as a base class

Комментарий идет в шапке и / или в документации. Если клиенты вашего класса не следуют инструкциям на пакете, то в C ++ они могут ожидать неопределенного поведения. Вывод без разрешения является лишь частным случаем этого. Вместо этого они должны использовать композицию.

Кстати, это немного вводит в заблуждение: «базовый класс должен иметь открытый и виртуальный деструктор или защищенный и не виртуальный».

Это верно для классов, которые должны использоваться в качестве основы для полиморфизма во время выполнения. Но это не обязательно, если производные классы никогда не будут ссылаться через указатели на тип базового класса. Может быть разумно иметь тип значения, который используется только для статического полиморфизма, например, с имитированным динамическим связыванием. Путаница заключается в том, что наследование может использоваться для различных целей в C ++, требуя различной поддержки со стороны базового класса. Это означает, что хотя вам не нужен динамический полиморфизм с вашим классом, тем не менее, было бы неплохо создавать производные классы при условии, что они используются правильно.

0 голосов
/ 16 июня 2009

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


Я давно не пользовался C ++, но, насколько я помню, вы получаете то, что хотите, делая приватный деструктор.

UPDATE:

В Visual Studio 2005 вы получите либо предупреждение, либо ошибку. Проверьте следующий код:

class A
{
public:
    A(){}
private:
    ~A(){}
};

class B : A
{
};

Теперь

B b;

выдаст ошибку "ошибка C2248: 'A :: ~ A': невозможно получить доступ к закрытому члену, объявленному в классе 'A'"

пока

B *b = new B();

выдаст предупреждение «предупреждение C4624:« B »: деструктор не может быть сгенерирован, потому что деструктор базового класса недоступен».

Похоже на половинное решение, НО, как указал орсогуфо, делая класс А непригодным для использования. Оставляя ответы

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...