【问题标题】:Preventing Virtual Method Implementation in C++防止 C++ 中的虚拟方法实现
【发布时间】:2009-02-14 21:32:00
【问题描述】:

我在 C++ 中有以下类层次结构:

class Base {
    virtual void apply() = 0;
};

class Derived : public Base {
    virtual void apply() {
        // implementation here that uses derived_specialty
    }

    virtual void derived_specialty() = 0;
};


class Implementation : public Derived {   
    virtual void derived_specialty() {
        // implementation
    }
};

我想保证实现级别的类不提供它们自己的应用实现,并且它们只实现派生专业。有没有办法保证继承自 Derived 的类不会实现 apply,从而使用 Derived::apply 实现?我的理解是,在 C++ 中,基类中的虚拟方法在继承层次结构中一直是虚拟的,但是如果 C++ 中有任何技巧可以完成,我很想听听它们。

我总是对 C++ 允许的事情感到惊讶,所以我认为值得一问。 :)

【问题讨论】:

    标签: c++ inheritance encapsulation


    【解决方案1】:

    您可以将实现作为委托类而不是派生的特化

    class Derived : public Base
    {
        Derived()
    
        void apply() 
        {
            //whatever, delegate to impl class instance
            impl->apply_specialization();
        }
    
    
        Impl* impl;
    };
    
    class Impl : public WhateverImplInterface
    {
          void apply_specialization(){}
    };
    

    然后实现无权访问 apply 函数并与层次结构分离。 Derived 类然后由 Impl 类的实例参数化。

    【讨论】:

    • 我更喜欢给类命名比 Impl 更有意义(我假设它只是示例的抽象)。它使搜索代码更容易,也不太可能混淆我的 IDE。
    • @Dave,是的,这只是在示例上下文中的一般性
    • Nitpick 角即将推出。
    【解决方案2】:

    你可以通过组合来做到这一点:

    class Base {
        virtual void apply();
    };
    
    class Derived : public Base {
    
        class IImplementation {
            virtual void derived_specialty() = 0;
        };
    
        IImplementation& m_implementation;
    
        Derived(IImplementation& implementation)
            : m_implementation(implementation)
        {}
    
        virtual void apply() {
            // implementation here that uses m_implementation.derived_specialty
        }
    
    };
    
    
    class Implementation : Derived::IImplementation {   
        virtual void derived_specialty() {
            // implementation
        }
    };
    

    其他类仍然可以继承 Derived 并覆盖 apply 方法,但您的 Implementation 类不再是这些类之一。

    【讨论】:

    • 你会称之为“组合”吗?也许“代表团”是一个更好的名字。
    • 好主意!但是 Base 派生位呢?
    • @strager 我不明白你的问题:什么bits,是的,什么关于它们?
    • 实现现在不是从 Base 派生的,这是一个问题。 Derived 也没有。
    • 我的错误:我已经编辑以显示从 Base 派生的派生。感谢您指出它已经出来了。但是,我没有看到实施不是从 Base 派生的问题。
    【解决方案3】:

    在您的文档中明确限制。

    【讨论】:

      【解决方案4】:

      “我想保证实现级别的类不提供自己的应用实现。”

      你不能。

      到目前为止,我所看到的示例都没有阻止任何派生类定义自己的应用函数。它们都提供了对 apply 和 derived_specialty 之间的关系进行建模的方法,向用户建议他们不应覆盖 apply。不过,您可以在一行文档中实现相同的目标。

      您要查找的是 C++ 中不存在的 Java final 语句,对吧?

      【讨论】:

        【解决方案5】:

        您可以使 Base::apply 成为非虚拟的,并在 Base 中使用模板方法模式。

        本文解释了这种做法的优点:
        http://www.gotw.ca/publications/mill18.htm

        【讨论】:

          【解决方案6】:

          您可以在析构函数中放置一个断言以确保 apply 未被覆盖:

          class Base {
              virtual void apply() = 0;
          };
          
          class Derived : public Base {
              virtual void apply() {
                  // implementation here that uses derived_specialty
              }
              virtual ~Derived() {
                  assert(this->apply == Derived::apply);
              }
              virtual void derived_specialty() = 0;
          };
          
          
          class Implementation : public Derived {   
              virtual void derived_specialty() {
                  // implementation
              }
          };
          

          这里的想法是 this->apply 将从虚拟表中获取方法地址,而 Derived::apply 在编译时解析它。如果它们相等,则 apply 在 Implementation 类中不会再次被覆盖。这种方法还有一个优点是它不会在发布版本中施加性能损失,其中 assert() 宏被(应该)从生成的代码中剥离。

          【讨论】:

          • 也许我只是没有掌握一些东西,但如果 apply() 被覆盖,那么断言代码将永远不会被调用,不是吗?如果断言被检查过,那么根据定义,我们在正确的 apply() 方法中......
          • 你是对的,最好将 assert() 放在析构函数中,这样你就可以确定它会在某个时候被调用。
          • 当然这只会在事后给程序员一个警告。如果您知道在 apply() 之前会调用另一个方法,则可以将 assert() 放入其中。
          • 实际上,我只是尝试过,它甚至无法编译... this->apply 构造无效(&this->apply 或我能想到的任何其他构造也无效)。也许在标准 C++ 中根本不可能做到这一点......
          • 在哪个编译器中?在发布示例之前,我在 VS2005 中编译它,没有任何抱怨。
          【解决方案7】:

          尝试使用模板方法模式

          Wikipedia 有一个 C++ 示例。

          它不会改变封装,但它改进了设计,所以你不需要。

          【讨论】:

            【解决方案8】:

            总是有访问修饰符:

             class base {
                  protected: virtual void real_apply() = 0;
             };
             class derived : public base {
                  void real_apply();
             public:
                  apply() { real_apply(); }
             };
             class other : public derived {
                  void func() {
                      apply();      // this is ok
                      real_apply(); // this is a compile time error
                  }
             };
            

            【讨论】:

              猜你喜欢
              • 2013-03-08
              • 1970-01-01
              • 1970-01-01
              • 2014-11-05
              • 2012-05-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-09-09
              相关资源
              最近更新 更多