Недавно один из моего друга спросил меня, как предотвратить наследование классов в 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 и виртуальное наследование
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 не будет наследован virutally. Почему это? Это имеет какое-либо отношение к vdisp ptr??
PS:
Вышеупомянутая программа была скомпилирована в компиляторе C++ MS (Visual Studio). ссылка: http://www.codeguru.com/forum/archive/index.php/t-321146.html
Вы не можете предотвратить наследование (до ключевого слова C++11 final
) - вы можете предотвратить только инстанцирование наследуемых классов. Другими словами, нет никакого способа предотвратить:
class A { ... };
class B : public A { ... };
Лучшее, что Вы можете сделать, это предотвратить инстанцирование объектов типа B. Исходя из этого, я предлагаю воспользоваться советом kts и задокументировать тот факт, что A (или что-то в этом роде) не предназначен для наследования, дать ему невиртуальный деструктор, и никакие другие виртуальные функции, и оставить все как есть.
Вы проходите через конторшны, чтобы предотвратить дальнейшее подклассирование. Почему? Зафиксируйте тот факт, что класс не является расширяемым, и сделайте dtor невиртуальным. В духе с, если кто-то действительно хочет игнорировать то, как вы намеревались использовать это, зачем их останавливать? (Я также никогда не видел смысла окончательных
классов/методов в java).
//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
DontExtened();
/*NOT VIRTUAL*/
~DontExtened();
...
};
-121--989134- При простой настройке (когда сервер приложений сервлетов принимает запрос клиента напрямую) оба метода возвращают одно и то же значение.
Если контейнер сервлета не является приложением, принимающим запрос клиента (например, при использовании HTTP-сервера Apache с mod_jk для принятия запроса и пересылки запроса экземпляру Tomcat через AJP), то getServerPort ()
вернет порт, к которому подключен клиент (вероятно, 80, когда используется порт по умолчанию) и getLocalPort ()
вернет порт, который Tomcat использовал для приема соединения с HTTP-сервера (вероятно, 8009 или что-то подобное).
1) - это вопрос вкуса. Если я вижу это правильно, ваши более причудливые 2-е и 3-е решения перемещают ошибку в определенных обстоятельствах со времени ссылки на время компиляции, которое в целом должно быть лучше.
2) Виртуальное наследование необходимо для принудительной инициализации (виртуального) базового класса наиболее производным классом, из которого ctor базового класса уже недоступен.
Вы проходите через искажения, чтобы предотвратить дальнейшие подклассификации. Почему? Задокументируйте тот факт, что класс не является расширяемым и сделайте дтор невиртуальным. В духе c, если кто-то действительно хочет проигнорировать то, как вы собирались это использовать, зачем останавливать его? (Я никогда не видел смысла заключительных
классов/методов и в java).
//Note: this class is not designed to be extended. (Hence the non-virtual dtor)
struct DontExtened
{
DontExtened();
/*NOT VIRTUAL*/
~DontExtened();
...
};
Если можно, я бы выбрал первый вариант (частный конструктор). Причина в том, что практически любой опытный программист на Си++ увидит это с первого взгляда и сможет распознать, что вы пытаетесь предотвратить подклассификацию.
Могут быть и другие, более хитрые методы предотвращения подклассов, но в этом случае чем проще, тем лучше.
To ответьте на ваш вопрос, вы не можете наследовать от 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>
{
};
Еще одно решение:
template < class T >
class SealedBase
{
protected:
SealedBase()
{
}
};
#define Sealed(_CLASS_NAME_) private virtual SealedBase<_CLASS_NAME_>
#include "Sealed.h"
class SomeClass : Sealed(Penguin)
{
};