【问题标题】:Prevent class inheritance in C++防止 C++ 中的类继承
【发布时间】:2011-01-12 03:38:06
【问题描述】:

最近我的一位朋友问我如何防止 C++ 中的类继承。他希望编译失败。

我想了想,找到了 3 个答案。不知道哪个是最好的。

1) 私有构造函数

class CBase
{

public:

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

private:

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


};

2) 使用 CSealed 基类、私有 ctor 和虚拟继承

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. 除非CSealed 类被虚拟继承,否则方法2 和3 将不起作用。这是为什么 ?和vdisp ptr有关系吗??

PS:

上述程序是在 MS C++ 编译器 (Visual Studio) 中编译的。 参考:http://www.codeguru.com/forum/archive/index.php/t-321146.html

【问题讨论】:

    标签: c++ inheritance controls


    【解决方案1】:

    您正在通过扭曲来防止进一步的子类化。为什么?记录该类不可扩展并使 dtor 非虚拟的事实。本着 c 的精神,如果有人真的想忽略您打算使用它的方式,为什么要阻止他们? (我也从未在 java 中看到 final 类/方法的意义)。

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

    【讨论】:

    • 我认为Java的重点是如果类是final的,JIT编译器可以优化对虚拟方法的调用
    • 这有点跑题了。在 Java 中,final 关键字很有意义,因为默认情况下所有函数都是虚拟的,因此允许 JIT 编译器执行这些优化有很多好处。在 C++ 中,阻止子类化的类似机制不会有任何好处,这就是为什么该语言不提供这样做的机制的原因。
    • -1:这种扭曲是为了事故。此外,这并不能回答 OP 的问题(即使它可能(它不是)是 X-Y 问题)。您的答案相当于公开所有内容并记录每个逻辑上的私有项目。我的规则是尽可能让编译器帮助你不犯错误。
    • 我认为 C++11 有 final 就是为了这个目的。因此,我希望这是在 C++11 中这样做的惯用方式。当你有一个语言特性来执行 cmets 尝试(但失败)强制执行的操作时,为什么还要使用 cmets?
    • 我同意@ThomasEding final 标识符是更好的选择。它允许编译器为您提供帮助,并且非常准确地记录了这个类不是为了继承而设计的。如果您选择采用 cmets 的路径,那么您将让程序员查找源代码并解释注释。这可以是多行,甚至可能在更大的评论块中。通过使用final,IDE 可以在第一次尝试从类继承时提供帮助。除了使用final,即使他/她不习惯使用的编程风格,开发人员也知道去哪里找。
    【解决方案2】:

    1) 是一个品味问题。如果我没看错的话,您更花哨的第 2 和第 3 解决方案在某些情况下将错误从链接时间转移到编译时间,这通常应该更好。

    2) 需要虚拟继承来强制将(虚拟)基类初始化为基类 ctor 不再可访问的最派生类。

    【讨论】:

      【解决方案3】:

      您不能阻止继承(在 C++11 的 final 关键字之前) - 您只能阻止继承类的实例化。换句话说,没有办法阻止:

      class A { ... };
      
      class B : public A { ... };
      

      您能做的最好的事情就是阻止实例化类型 B 的对象。在这种情况下,我建议您听取 kts 的建议并记录 A(或其他)不打算用于继承的事实,给它一个非虚拟析构函数,而不是其他虚拟函数,然后就这样。

      【讨论】:

      • +1:你无法阻止有人选择使用继承而不是组合,即使我们(和宇宙的其他人)可能不同意。记录它
      • 请注意,在 C++11 中,您可以轻松阻止继承。
      【解决方案4】:

      如果可以,我会选择第一个选项(私有构造函数)。原因是几乎任何有经验的 C++ 程序员都会一眼就看出这一点,并且能够识别出您正在尝试阻止子类化。

      可能还有其他更棘手的方法来防止子类化,但在这种情况下,越简单越好。

      【讨论】:

        【解决方案5】:

        要回答您的问题,您不能从 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>
        {
        };
        

        【讨论】:

        • 它必须是 CBase 类:私有虚拟 CSealed。否则,可以导出 CBase。
        【解决方案6】:

        从 C++11 开始,您可以在类中添加 final 关键字,例如

        class CBase final
        {
        ...
        

        我想这样做的主要原因(以及我来寻找这个问题的原因)是将一个类标记为不可子类化,这样您就可以安全地使用非虚拟析构函数并完全避免使用 vtable。

        【讨论】:

        • 还有一个很好的理由,那就是防止派生类打破不可变类的契约。
        • @Nemanja Boric 将适用于任何子类和任何合约,而不仅仅是可变性。任何子类都可能破坏该类的任何隐含契约——这并不是禁止所有子类的真正好理由。对于不可变对象,如果您想添加派生值,例如 FirstName() 和 LastName() 方法中的 FullName(),或者可能是特定的哈希函数,该怎么办。
        【解决方案7】:
        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

        【讨论】:

          【解决方案8】:

          详细说明Francis' answer:如果类Bottom派生自类Middle,而后者实际上继承自类Top,那么负责构造虚拟继承的就是大多数派生类(Bottom)基类 (Top)。否则,在多重继承/死亡钻石场景(经典使用虚拟继承)中,编译器将不知道两个“中间”类中的哪一个应该构造单个基类。因此,当从 Bottom 构造 Middle 时,Middle 的构造函数对 Top 的构造函数的调用将被忽略:

          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(),因为它是在 @ 中私下继承的(默认情况下,就像在您的代码中一样,但值得明确表示) 987654335@,因此在Bottom 中不可见。 (source)

          【讨论】:

            【解决方案9】:

            从 C++11 开始,有一个干净的解决方案,我很惊讶在这里没有看到。我们可以使类 final 防止任何进一步的继承。

            class Foo final {};
            
            class Bar: public Foo {}; // Fails to compile as Foo is marked as final
            

            【讨论】:

            • 自 2012 年以来,an answer 一直在指出 C++11 final 关键字;它目前的最高票数约为 6
            猜你喜欢
            • 1970-01-01
            • 2015-03-17
            • 1970-01-01
            • 2020-06-01
            • 2014-01-07
            • 1970-01-01
            • 2016-11-14
            • 1970-01-01
            相关资源
            最近更新 更多