Можно ли запретить вывод из класса во время компиляции?

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

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

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

Ответ 1

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

Ответ 2

Bjarne Stroustrup написал об этом здесь.


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

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

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

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

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

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

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

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).

Ответ 3

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

class underivable {
    underivable() { }
    underivable(const underivable&); // not implemented
    underivable& operator=(const underivable&); // not implemented
public:
    static underivable create() { return underivable(); }
};

Ответ 4

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

Ответ 5

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

Это выполняется в время компиляции.

Ответ 6

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

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

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

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

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

Ответ 7

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


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

UPDATE:

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

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

class B : A
{
};

Теперь

B b;

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

while

B *b = new B();

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

Похоже на полусолютий, НО, как указано orsogufo, это делает класс A непригодным для использования. Остальные ответы