Есть ли способ запретить подкласс класса?

Скажем, у меня есть класс под названием "Base" и класс под названием "Derived", который является подклассом Base и доступ к защищенным методам и членам Base.

Теперь я хочу сделать так, чтобы никакие другие классы не могли подклассифицировать Derived. В Java я могу это сделать, объявив класс Derived "final". Есть ли какой-то С++ трюк, который может дать мне тот же эффект?

(В идеале я хотел бы сделать так, чтобы ни один класс, кроме Derived, не мог также подкласса Base. Я не могу просто поместить весь код в тот же класс или использовать ключевое слово friend, так как Base и Derived оба tempated, с базой, имеющей меньше аргументов шаблона, чем Derived делает....)

Ответы

Ответ 1

Начиная с С++ 11, вы можете добавить к вашему классу ключевое слово final (технически специальный идентификатор, поскольку оно не является ключевым словом), например

class Derived final
{
...

Вы можете узнать больше о ключевом ключе в http://en.wikipedia.org/wiki/C++11#Explicit_overrides_and_final

Ответ 2

У вас может быть частный конструктор для 'Derived' и общедоступная статическая функция Create для создания экземпляра

Ответ 3

Самый простой способ запретить подклассирование состоит в том, чтобы сделать конструктор закрытым:

class Foo
{
private:
    Foo() {}

public:
    static Foo* CreateFoo() { return new Foo; }
};

Изменить: благодаря Indeera, указав, что для этого требуется статический метод Factory

Ответ 4

Нет простого и чистого способа сделать это.

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

В конечном счете, вам нужно сделать абсолютно невозможным подклассирование? Разве это не достаточно хорошо, чтобы указать, что "выход из этого класса - плохая идея"?

Люди всегда могут сломать ваш код, если они этого действительно хотят. Лучшее, что вы можете сделать, это сделать их осведомленными о том, что они должны и не должны делать, и надеемся, что они не будут активно пытаться сломать ваш код.

Защитите свой код от Мерфи, а не Макиавелли.;)

Ответ 5

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

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

Я использую COMMON, чтобы указать общий параметр шаблона между Base и Derived и EXTRA, чтобы обозначить дополнительные параметры, которые, по вашему словам, имеют Derived. Фактическое число из них может быть тем, что я только что выбрал для этого, соответственно, соответственно.

// Forward declaration of class Derived
template< class COMMON
        , class EXTRA1
        , class EXTRA2 >
class Derived;


// Definition of general class template Base
template< class SUBCLASS
        , class COMMON >
class Base
{
private:
    Base() {}
};


// Definition of partial specialisation of template class Base to open up
// access to the constructor through friend declaration.
template< class COMMON
        , class EXTRA1
        , class EXTRA2 >
class Base< Derived< COMMON, EXTRA1, EXTRA2 >
          , COMMON >
{
private:
    Base() {}

    friend class Derived< COMMON, EXTRA1, EXTRA2 >;
};


// Definition of class Derived
template < class COMMON
         , class EXTRA1
         , class EXTRA2 >
class Derived
    : public Base< Derived< COMMON, EXTRA1, EXTRA2 >
                 , COMMON >
{
public:
    static Derived* create() { return new Derived; }

private:
    Derived() : Base< Derived< COMMON, EXTRA1, EXTRA2 >
                    , COMMON >()
    {
    }
};


// Definition of class HonestDerived.
// It supplies itself as the SUBCLASS parameter to Base.
template < class COMMON
         , class EXTRA1
         , class EXTRA2 >
class HonestDerived
    : public Base< HonestDerived< COMMON, EXTRA1, EXTRA2 >
                 , COMMON >
{
public:
    HonestDerived() : Base< HonestDerived< COMMON, EXTRA1, EXTRA2 >
                          , COMMON >()
    {
    }
};


// Definition of class DishonestDerived
// It supplies Derived rather than itself as the SUBCLASS parameter to Base.
template < class COMMON
         , class EXTRA1
         , class EXTRA2 >
class DishonestDerived
    : public Base< Derived< COMMON, EXTRA1, EXTRA2 >
                 , COMMON >
{
public:
    DishonestDerived() : Base< Derived< COMMON, EXTRA1, EXTRA2 >
                             , COMMON >()
    {
    }
};


template< class COMMON, class EXTRA1, class EXTRA2 >
class DerivedFromDerived
    : public Derived< COMMON, EXTRA1, EXTRA2 >
{
public:
    DerivedFromDerived() : Derived< COMMON, EXTRA1, EXTRA2 >()
    {
    }
};

// Test partial specialisation gives Derived access to the Base constructor
Derived< int, float, double >* derived
    = Derived< int, float, double >::create();

// Test that there is no access to the Base constructor for an honest subclass
// i.e. this gives a compiler error
HonestDerived< int, float, double > honestDerived;

// Test that there is no access to the Base constructor for a dishonest subclass
// i.e. this gives a compiler error
DishonestDerived< int, float, double > dishonestDerived;

// Test that there is no access to the Derived constructor
// i.e. this gives a compiler error
DerivedFromDerived< int, float, double > derivedFromDerived;

Этот код был протестирован с помощью gcc 4.3.2.

Обратите внимание, что альтернативой объявлению друга было бы сделать конструктор защищенным в частичной специализации Base, но затем это позволит использовать классы, такие как DishonestDerived.