2010-02-02 4 views
48

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

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

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

class CBase 
{ 

public: 

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

private: 

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


}; 

2) Используя CSealed базовый класс, частный т е р & виртуальное наследование

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

ответ

-1

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

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

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


#include "Sealed.h" 

class SomeClass : Sealed(Penguin) 
{ 
}; 
+3

Добавление макроса в микс не делает это новым решением. –

11

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

//Note: this class is not designed to be extended. (Hence the non-virtual dtor) 
struct DontExtened 
{ 
    DontExtened(); 
    /*NOT VIRTUAL*/ 
    ~DontExtened(); 
    ... 
}; 
+10

Я думаю, что точка в Java заключается в том, что компилятор JIT может оптимизировать вызовы виртуальных методов, если класс является окончательным – Manuel

+0

@Manuel. Я понимаю, что jvm может понравиться, но должен быть простой способ отменить, что без изменения источника. '@ReallyOverride?' – KitsuneYMG

+1

Это немного завышено. В Java ключевое слово «final» имеет смысл, потому что все функции являются виртуальными по умолчанию, и поэтому можно многое выиграть, разрешив компилятору JIT выполнять эти оптимизации. В C++ нечего было бы получить от аналогичного механизма для предотвращения подклассификации, поэтому язык не дает механизма для этого. – jalf

5

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

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

9

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

class A { ... }; 

class B : public A { ... }; 

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

+1

+1: Вы не можете остановить кого-то, кто хочет использовать наследование над композиционным материалом, даже если мы (и остальная вселенная) можем не согласиться. Документ –

+5

Обратите внимание, что в C++ 11 вы можете легко предотвратить наследование. –

1

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

Могут быть другие более сложные методы предотвращения подкласса, но в этом случае проще, тем лучше.

4

Чтобы ответить на ваш вопрос, вы не можете наследовать от 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

Это должен быть класс CBase: частный виртуальный CSealed . В противном случае можно получить CBase. – Jagannath

54

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

class CBase final 
{ 
... 

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

+2

Есть еще одна веская причина, и это мешает производным классам разорвать контракт неизменных классов. –

+0

@Nemanja Boric, который применим к любому подклассу и любому контракту, а не только к изменчивости. Любой подкласс может потенциально разорвать любые подразумеваемые контракты класса - это не является хорошей причиной для отказа от всех подклассов. Для неизменяемого объекта, если вы хотите добавить производное значение, например FullName() из методов FirstName() и LastName(), или, возможно, специфическую хеш-функцию. –

0
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

Чтобы уточнить Francis' answer: если класс Bottom является производным от класса Middle, который фактически наследует от класса Top, это то, что наиболее производный класс (Bottom), который отвечает за строительство фактически унаследованный базовый класс (Top). В противном случае в сценарии множественного наследования/алмаза смерти (где виртуальное наследование классически используется) компилятор не знает, какой из двух «средних» классов должен построить один базовый класс. «Вызов конструктору в к Top» The Middle конструктор s поэтому игнорируется при 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. (source)

Смежные вопросы