Предотвращение наследования классов в С++

Недавно один из моих друзей спросил меня, как предотвратить наследование класса на С++. Он хотел, чтобы компиляция потерпела неудачу.

Я думал об этом и нашел 3 ответа. Не уверен, какой из них лучше.

1) Частный конструктор (ы)

class CBase
{

public:

 static CBase* CreateInstance() 
 { 
  CBase* b1 = new CBase();
  return b1;
 }

private:

 CBase() { }
 CBase(CBase3) { }
 CBase& operator=(CBase&) { }


};

2) Использование базового класса CSealed, частного ctor и виртуального наследования

class CSealed
{

private:

 CSealed() {
 }

 friend class CBase;
};


class CBase : virtual CSealed
{

public:

 CBase() {
 }

};

3) Использование базового класса CSealed, защищенного ctor и виртуального наследования

class CSealed
{

protected:

 CSealed() {
 }

};

class CBase : virtual CSealed
{

public:

 CBase() {
 }

};

Все вышеперечисленные методы гарантируют, что класс CBase не может быть унаследован далее. Мой вопрос:

1) Какой из лучших методов? Любые другие доступные методы?

2) Метод 2 и 3 не будет работать, если класс CSealed наследуется вирусно. Почему это? Это имеет какое-либо отношение к vdisp ptr??

PS:

Вышеупомянутая программа была скомпилирована в компиляторе MS С++ (Visual Studio). ссылка: http://www.codeguru.com/forum/archive/index.php/t-321146.html

Ответ 1

С С++ 11 вы можете добавить ключевое слово final в свой класс, например

class CBase final
{
...

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

Ответ 2

Вы преследуете искажения для предотвращения дальнейшего подкласса. Зачем? Документируйте тот факт, что класс не расширяется и делает dtor не виртуальным. В духе c, если кто-то действительно хочет игнорировать то, как вы намеревались использовать это, зачем останавливать их? (Я никогда не видел точки final классов/методов в java).

//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
  DontExtened();
  /*NOT VIRTUAL*/
  ~DontExtened();
  ...
};

Ответ 3

Вы не можете предотвратить наследование (до ключевого слова С++ 11 final) - вы можете только предотвратить создание экземпляров унаследованных классов. Другими словами, невозможно предотвратить:

class A { ... };

class B : public A { ... };

Лучшее, что вы можете сделать, - не создавать экземпляры объектов типа B. В этом случае я предлагаю вам обратиться к kts за советом и документировать тот факт, что A (или что-то еще) не предназначено для использования наследования, придать ему не виртуальный деструктор и другие виртуальные функции и оставить его на этом.

Ответ 4

1) - дело вкуса. Если я вижу это правильно, ваши более причудливые 2-е и 3-е решения перемещают ошибку в определенных обстоятельствах из времени ссылки для компиляции времени, что в целом должно быть лучше.

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

Ответ 5

Чтобы ответить на ваш вопрос, вы не можете наследовать от CBase, потому что в виртуальном наследовании производный класс должен иметь прямой доступ к классу, из которого он был унаследован практически. В этом случае класс, который будет получен из CBase, должен иметь прямой доступ к CSealed, который он не может, поскольку конструктор является закрытым.

Хотя я не вижу полезности всего (т.е. прекращения наследования), вы можете обобщить использование шаблонов (я не думаю, что он компилируется на всех компиляторах, но он работает с MSVC)

template<class T>
class CSealed
{
    friend T;    // Don't do friend class T because it won't compile
    CSealed() {}
};

class CBase : private virtual CSealed<CBase>
{
};

Ответ 6

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

Могут быть другие более сложные методы предотвращения подклассификации, но в этом случае проще, тем лучше.

Ответ 7

class myclass;

    class my_lock {
        friend class myclass;
    private:
        my_lock() {}
        my_lock(const my_lock&) {}
    };

    class myclass : public virtual my_lock {
        // ...
    public:
        myclass();
        myclass(char*);
        // ...
    };

    myclass m;

    class Der : public myclass { };

    Der dd;  // error Der::dd() cannot access
            // my_lock::my_lock(): private  member

Я нашел его здесь, чтобы отдать должное. Я размещаю здесь только другие люди могут легко получить доступ http://www.devx.com/tips/Tip/38482

Ответ 8

Выяснить ответ Фрэнсиса: если класс Bottom происходит из класса Middle, который фактически наследуется от класса Top, то это то, что большинство полученных class (Bottom), который отвечает за построение фактически унаследованного базового класса (Top). В противном случае в сценарии множественного наследования/алмаза смерти (где виртуальное наследование классически используется) компилятор не знает, какой из двух "средних" классов должен построить один базовый класс. Поэтому вызов конструктора Middle конструктору Top игнорируется, когда Middle строится из Bottom:

class Top {
    public:
        Top() {}
}

class Middle: virtual public Top {
    public:
        Middle(): Top() {} // Top() is ignored if Middle constructed through Bottom()
}

class Bottom: public Middle {
    public:
        Bottom(): Middle(), Top() {}
}

Итак, в подходе 2) или 3) в вашем вопросе Bottom() не может вызывать Top(), потому что он унаследовал конфиденциально (по умолчанию, как и в вашем коде, но стоит сделать его явным) в Middle и, следовательно, не отображается в Bottom. (источник)

Ответ 9

Еще одно решение:

template < class T >
class SealedBase
{
protected:
    SealedBase()
    {
    }
};

#define Sealed(_CLASS_NAME_) private virtual SealedBase<_CLASS_NAME_>


#include "Sealed.h"

class SomeClass : Sealed(Penguin)
{
};