有没有办法禁止我的类的子类化?

Posted

技术标签:

【中文标题】有没有办法禁止我的类的子类化?【英文标题】:Is there a way to forbid subclassing of my class? 【发布时间】:2010-11-17 20:11:55 【问题描述】:

假设我有一个名为“Base”的类,以及一个名为“Derived”的类,它是 Base 的子类,可以访问 Base 的受保护方法和成员。

我现在要做的就是让它没有其他类可以继承 Derived。在 Java 中,我可以通过将 Derived 类声明为“final”来实现这一点。有什么 C++ 技巧可以给我同样的效果吗?

(理想情况下,我想让除 Derived 之外的任何类都不能继承 Base。我不能只将所有代码放入同一个类或使用friend关键字,因为 Base 和 Derived 都是模板化,Base 的模板参数比 Derived 少....)

【问题讨论】:

C++ FAQ Lite 对此有 a topic。 我会包含一些源代码 - 因为它涉及模板。 确实如此……然而,这些技术似乎都不是完全令人满意的:#1 意味着 Derived 的用户必须使用非标准的习惯用法才能使用该类;他们不能像往常一样直接声明它;相反,您必须调用命名构造函数。 #2 is not 不会在尝试子类化时产生任何编译时错误(这是我正在寻找的效果) #3 将向我的对象添加一个 vtable,我希望避免这种情况。 在这种情况下,您的问题(是否有技术)的答案几乎肯定是“否”。 @Jeremy - 没错,正如大卫建议的那样,你可能不走运。 【参考方案1】:

您可以为 'Derived' 使用私有构造函数,并为实例化使用公共静态 Create 函数

【讨论】:

【参考方案2】:

禁止子类化的最简单方法是将构造函数设为私有:

class Foo

private:
    Foo() 

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

编辑:感谢 Indeera 指出这需要一个静态工厂方法

【讨论】:

啊,但是您将如何创建 Foo 的实例? :) 其实Foo::CreateFoo(),虽然它为什么返回一个指针对我来说是个谜,因为这个类可以完美地复制...... 你说的最简单的方法。有哪些替代方案?【参考方案3】:

没有简单而干净的方法。

标准库所做的只是使析构函数成为非虚拟的。这不会阻止子类化,但它向用户发出了一个强烈的信号,表明它不是为继承而设计的,这意味着在使用派生类时必须非常小心。

但最终,您是否需要绝对让子类化不可能?表明“从这个类派生是一个坏主意”不是很好吗?

如果他们真的愿意,人们总是可以破坏你的代码。你能做的最好的就是让他们知道他们应该做什么和不应该做什么,并希望他们不会主动尝试破坏你的代码。

保护您的代码免受墨菲的侵害,而不是马基雅维利。 ;)

【讨论】:

是否有工具会在您忽略此“强信号”时发出警告。【参考方案4】:

由于您使用的是模板,我认为您关于防止除 Derived 以外的任何类从 Base 到子类的问题的最后一部分可以使用适当的部分专业化来完成。

以下代码 sn-p 是我想出的,但所需的复杂性只会加强 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 这样的类工作。

【讨论】:

【参考方案5】:

从 C++11 开始,您可以将 final 关键字(技术上是一个特殊的标识符,因为它实际上不是关键字)添加到您的类中,例如

class Derived final

...

你可以在http://en.wikipedia.org/wiki/C++11#Explicit_overrides_and_final阅读更多关于final关键字的信息

【讨论】:

+1 使用 C++11 提供此解决方案很有趣。指向更详细描述的附加链接将使此答案更有帮助。

以上是关于有没有办法禁止我的类的子类化?的主要内容,如果未能解决你的问题,请参考以下文章

子类化 UITextField

如何使用 Fluent NHibernate 自动映射禁用特定抽象基类的子类化

有没有办法“子类化”修饰符?

有没有办法禁止转换为 C++ 中非常量的子类?

C++实现一个不能被继承的类

虚函数本质