Как запретить публичное наследование, но разрешить частное (и защищенное) наследование
В С++ 11 было введено ключевое слово final
, чтобы запретить будущие переопределения или запретить наследование. Наиболее распространенный пример, где он может использоваться, относится к классам, которые не предназначены для использования в качестве базовых классов (например, не виртуальных деструкторов). Тем не менее, иногда мы можем захотеть иметь отношение is-реализованное-в-термины между двумя классами (т.е. private
inheritance), а не is-a отношение (public
наследование). Однако final
запрещает оба типа наследования.
Мой вопрос заключается в следующем: существует ли способ разрешить наследование private
, но запрещает наследование public
(возможно, не напрямую, но по крайней мере мы можем "имитировать" его)? В этом случае проблем не будет, даже если мы будем использовать класс с не виртуальным деструктором, так как мы не можем напрямую использовать производный класс с помощью указателя на базу, поэтому мы должны быть в порядке.
Я думаю о таком коде:
class Base /*final*/ {}; // making it final prohibits both private and public inheritance
class PrivateDerived: private Base{}; // this should work
class PublicDerived: public Base{}; // this shouldn't
int main()
{
PrivateDerived prvd;
PublicDerived pubd; // this should not compile
// Base* pBase = new PrivateDerived; // doesn't work, so we are ok
}
Ответы
Ответ 1
Интересный вопрос! Если вы не возражаете отказаться от тривиальности деструктора, я думаю, что следующее выполняет эту работу:
#include <type_traits>
template <typename T>
class Base {
protected:
~Base() {
static_assert(!std::is_convertible<T*,Base*>::value, "Invalid use of public inheritance.");
}
};
class Derived : public Base<Derived> {
};
int main() {
Derived d;
}
Не удалось скомпилировать следующий код: static_assert
срабатывает, потому что Derived*
конвертируется в Base<Derived>*
. Однако, если вы измените наследование на protected
или private
, тогда компиляция кода.
К сожалению, пользователи все еще могут стрелять в ногу:
class Bad : public Base<Derived> {
};
Ответ 2
Я не уверен, что это то, что вы ищете, или если это поможет вам в вашем случае. Однако я продемонстрирую полиморфное поведение.
Защищенный конструктор Абстрактный класс
class BaseProtected {
// ----- Member Variable Section -----
public:
// There Shouldn't Be Public Variables In A Base Class Unless
// That Is The Behavior You Are Looking For - Keep In Mind
// Every Inherited Class & Outside Class Can Change Them.
protected:
// Member Variables Here To Be Shared With Each Derived Class
private:
// Member Variables Here To Be Used By Base Class Only
// ----- Member Function Section -----
public:
virtual ~BaseProtected(); // Virtual Destructor
void somefunc() const; // Common Function Between All Derived Class
virtual void allDerivedMustImplement() const; = 0 // Purely Virtual
protected:
// Default Constructor - Can Not Declare An Instance Of Base Class
BaseProtected(); // Abstract Class
// Protected Functions Shared Between Classes
// Protected Functions That Are Purely Virtual If Needed
private:
// Private Functions Used By Base Class Only
}; // BaseProtected
Производный класс с возможным наследованием
class DerivedWithPossibleInheritance : public BaseProtected {
// ----- Member Variable Section -----
public:
// Public Member If Giving Free Access
protected:
// Protected Members If Being Inherited From
private:
// Private Members Unique To This Derived Class
// ----- Member Function Section -----
public:
DerivedWithPossibleInheritance(); // Default Constructor
virtual ~DerivedWithPossibleInheritance(); // Virtual Destructor
void uniqueFunctionForThisClass() const;
void allDerivedMustImplement() const override;
private:
// Private Functions Unique To This Class
}; // DerivedWithPossibleInheritance
Производный класс, который нельзя унаследовать от
class DerivedClassCanNotBeInheritedFrom sealed : public BaseProtected {
// ----- Member Variable Section -----
public:
// Public Members Variables
protected:
// Should Not Have Member Variables Here For This Class Can Not Be Inherited from
private:
// Private Members Variables
// ----- Member Function Section ------
public:
DerivedClassCanNotBeInheritedFrom(); // Default Constructor
virtual ~DerivedClassCanNotBeInheritedFrom(); // Default Virtual Destructor
void anotherUniqueFunctionForThisClass() const;
void allDerivedMustImplement() const override;
protected:
// There Should Not Be Any Functions Here This Can Not Be Inherited From
private:
// Private Member Functions Here
}; // DerivedClassCanNotBeInheritedFrom
Я продемонстрировал здесь ключевое слово, запечатанное при работе с наследованием и полиморфизмом. Если вы не хотите, чтобы ваш класс был получен из последующего использования, используйте запечатанное ключевое слово.
Что касается объявления любого базового класса, отдельного класса или одного синглетного класса с частным конструктором, то необходимо использовать ключевое слово friend. Существует множество типов реализаций, которые могут быть задействованы здесь, чтобы показать их, но концепция предотвращения унаследования класса одинакова. Лично я не использовал ключевое слово final, но я использовал ключевое слово запечатанное, и оно работает очень хорошо.
Я не использовал наследование классов, кроме публичного: поэтому, чтобы ответить на ваши вопросы с точки зрения защищенного или частного наследования, я не очень хорошо знаком. Но, возможно, использование ключевого слова sealed может помочь вам.