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

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

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

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


person Jeremy Friesner    schedule 07.08.2009    source источник
comment
C ++ FAQ Lite содержит тема для этого.   -  person Sean Bright    schedule 08.08.2009
comment
Я бы включил некоторый исходный код, поскольку он включает в себя шаблоны.   -  person sylvanaar    schedule 08.08.2009
comment
Так и есть ... однако ни один из этих методов не кажется полностью удовлетворительным: №1 означает, что пользователи Derived должны использовать нестандартную идиому для использования класса; они не могут просто заявить об этом прямо, как обычно; вместо этого вам нужно вызвать именованный конструктор. # 2 не будет генерировать никаких ошибок времени компиляции, если будет предпринята попытка создания подкласса (это тот эффект, который я ищу). # 3 Добавлю vtable к моим объектам, чего я бы предпочел избежать.   -  person Jeremy Friesner    schedule 08.08.2009
comment
В этом случае ответ на ваш вопрос (есть ли методика) почти наверняка нет.   -  person David Thornley    schedule 08.08.2009
comment
@ Джереми - Верно, как сказал Дэвид, возможно, тебе не повезло.   -  person Sean Bright    schedule 08.08.2009
comment
Если у класса нет виртуального деструктора, вам, вероятно, не следует делать из него производные (это просто хорошее программирование). C ++ признает, что иногда вам нужна возможность расширять границы, и позволяет вам в любом случае наследовать от этого. Так что ваша проблема не в языковом, а в образовательном.   -  person Martin York    schedule 08.08.2009
comment
Не уверен, почему за это проголосовали так высоко (?) Разве этот ответ не эквивалент RTFM?   -  person Alan    schedule 08.08.2009
comment
@Alan: Это больше похоже на то, чтобы прочитать эту страницу руководства. Вторая ссылка (тема) ведет непосредственно к ответу. «RTFM» подразумевает, что ответ существует в документации, но не указывает где.   -  person Conspicuous Compiler    schedule 08.08.2009
comment
Голосование: удалите ключевое слово / последний тег, но вместо этого добавьте тег производного класса   -  person fmuecke    schedule 08.12.2009
comment
-1 Ссылка - это не ответ. При необходимости цитируйте соответствующие разделы цитируемого текста, но на самом деле напишите ответ.   -  person Lightness Races in Orbit    schedule 26.12.2011


Ответы (5)


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

class Derived final
{
...

Дополнительную информацию о последнем ключевом слове можно найти на странице http://en.wikipedia.org/wiki/C++11#Explicit_overrides_and_final

person Peter N Lewis    schedule 07.09.2012
comment
+1 Интересно иметь это решение с C ++ 11. Дополнительная ссылка на более подробное описание сделает этот ответ еще более полезным. - person Wolf; 27.02.2014

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

person Indy9000    schedule 07.08.2009

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

class Foo
{
private:
    Foo() {}

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

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

person Alan    schedule 07.08.2009
comment
А, но как бы вы создали экземпляр Foo? :) - person Indy9000; 08.08.2009
comment
Собственно Foo::CreateFoo(), хотя почему он возвращает указатель, для меня загадка, так как класс отлично копируется ... - person Matthieu M.; 08.12.2009
comment
Вы сказали самый простой способ. Какие есть альтернативы? - person Wolf; 27.02.2014

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

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

Однако, в конечном итоге, нужно ли вам полностью сделать создание подклассов невозможным? Разве не достаточно указать, что «унаследовать от этого класса - плохая идея»?

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

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

person jalf    schedule 07.08.2009
comment
Есть ли инструменты, которые предупреждают, если вы пропустите этот сильный сигнал. - person Wolf; 24.02.2014

Поскольку вы используете шаблоны, я подумал, что последняя часть вашего вопроса о предотвращении любого класса, кроме производного от подкласса от 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.

person Troubadour    schedule 08.08.2009