Предотвращение наследования классов в C ++ - PullRequest
56 голосов
/ 02 февраля 2010

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

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

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

class CBase
{

public:

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

private:

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


};

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

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 C ++ (Visual Studio).ссылка: http://www.codeguru.com/forum/archive/index.php/t-321146.html

Ответы [ 9 ]

70 голосов
/ 07 сентября 2012

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

class CBase final
{
...

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

11 голосов
/ 02 февраля 2010

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

class A { ... };

class B : public A { ... };

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

10 голосов
/ 02 февраля 2010

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

//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
  DontExtened();
  /*NOT VIRTUAL*/
  ~DontExtened();
  ...
};
5 голосов
/ 02 февраля 2010

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

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

4 голосов
/ 02 февраля 2010

Чтобы ответить на ваш вопрос, вы не можете наследовать от 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>
{
};
1 голос
/ 02 февраля 2010

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

0 голосов
/ 10 мая 2016

Чтобы уточнить ответ Фрэнсиса : если класс Bottom является производным от класса Middle, который фактически наследует от класса Top, то этот самый производный класс (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. ( источник )

0 голосов
/ 21 марта 2013
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

0 голосов
/ 02 февраля 2010

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

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

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


#include "Sealed.h"

class SomeClass : Sealed(Penguin)
{
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...