【问题标题】:Overriding a virtual function with a covariant return type in a template derived class在模板派生类中用协变返回类型覆盖虚函数
【发布时间】:2020-11-05 05:24:33
【问题描述】:

我想覆盖模板派生类中的虚函数。但是,我想使用派生类作为返回类型。下面是对应的代码:

class Abstract {
  public:
    virtual Abstract* allocate() const = 0;
};
template <typename Derived>
class Base : public Abstract {
  public:
    Derived* allocate() const override {
      return new Derived;
    }
};
class Concrete : public Base<Concrete> {
  public:
};
int main() {
  Concrete c;
  delete c.allocate();
}

不幸的是,我的编译器无法识别 Derived 实际上是从 Abstract 派生的,并且失败并显示以下错误消息。

mwe.cpp: In instantiation of ‘class Base<Concrete>’:
mwe.cpp:12:25:   required from here
mwe.cpp:8:14: error: invalid covariant return type for ‘Derived* Base<Derived>::allocate() const [with Derived = Concrete]’
     Derived* allocate() const override {
              ^~~~~~~~
mwe.cpp:3:23: note: overridden function is ‘virtual Abstract* Abstract::allocate() const’
 virtual Abstract* allocate() const = 0;
                       ^~~~~~~~

allocate 函数移动到Concrete 类中可以解决问题,但在创建多个具体类时会导致代码重复。有没有办法让编译器知道Derived 实际上是从Abstract 派生的?

【问题讨论】:

    标签: c++ inheritance types


    【解决方案1】:

    您可以模拟协变返回类型:

    这里的重点是类Reintroduce。没有它allocate 将覆盖虚函数。有了它,它就隐藏了继承的函数。

    #include <iostream>
    
    class Abstract {
      public:
        virtual Abstract* allocate() const = 0;
    };
    
    namespace details{
        class Reintroduce {};
    
        template <typename Derived>
        class AllocImpl : public Abstract
        {
        public:
            Abstract* allocate() const override {
                return new Derived;
            }
        };
    }
    
    
    template <typename Derived>
    class Base : public details::AllocImpl<Derived> 
    {
      public:
        Derived* allocate(details::Reintroduce = {}) const 
        {
          return static_cast<Derived*>(details::AllocImpl<Derived>::allocate());
        }
    };
    
    class Concrete : public Base<Concrete> {
      public:
    };
    
    
    int main() {
      Concrete c;
      delete c.allocate();
    }
    

    如果我们可以在没有 AllocImpl 的情况下实现它,那就太好了,但这会使调用变得模棱两可。

    【讨论】:

      【解决方案2】:

      问题是ConcreteBase&lt;T&gt; 中不完整(Base&lt;T&gt; 也不完整)(对于任何 CRTP)。

      所以编译器还不知道Concrete 继承自Abstract

      可能的解决方案是不使用 CRTP,而是使用常规继承:

      template <typename Base>
      class Derived : public Base {
        public:
          Derived* allocate() const override {
            return new Derived;
          }
      };
      class ConcreteImpl : public Abstract {
        public:
      };
      
      using Concrete = Derived<ConcreteImpl>;
      

      Demo

      【讨论】:

      • 这很好,因为这样你就不需要演员表了。但是,如果您想返回包装在“智能指针”中的协变返回类型指针,该怎么办
      • 不幸的是,智能指针不是协变的:-/您可能仍然有两种方法:返回指针的协变方法,调用虚拟方法将结果包装在智能指针中的非虚拟方法.
      • 在您的示例中,您通过使用“中介”类来执行与“CRTP”相反的操作。好主意。
      【解决方案3】:

      修改Bernds idea的引入类型标签来区分不同的allocate功能,我们可以创建一个更简单的解决方案。诀窍是,我们在Abstract 类中引入了类型标记,并且我们在不提供默认参数的情况下覆盖了allocate 方法(以避免歧义)。以下解决方案的缺点是我们需要能够更改Abstract 类中allocate 方法的签名,这可能并不总是可行的。

      class BaseTag {};
      class Abstract {
        public:
          virtual Abstract* allocate(BaseTag = {}) const = 0;
      };
      
      template <typename Derived>
      class Base : public Abstract {
        public:
          Abstract* allocate(BaseTag) const override { return allocate(); }
          Derived* allocate() const {
            return new Derived;
          }
      };
      
      class Concrete : public Base<Concrete> {
        public:
      };
      
      int main() {
        Concrete c;
        delete c.allocate();
      
        Abstract* a = &c;
        delete a->allocate();
      }
      

      【讨论】:

      • 这也是个好主意。
      猜你喜欢
      • 2018-06-06
      • 1970-01-01
      • 1970-01-01
      • 2012-07-24
      • 2013-04-01
      • 2014-08-16
      • 1970-01-01
      • 2018-12-23
      相关资源
      最近更新 更多