【问题标题】:How can I use covariant return types with smart pointers?如何将协变返回类型与智能指针一起使用?
【发布时间】:2021-04-20 21:35:44
【问题描述】:

我有这样的代码:

class RetInterface {...}

class Ret1: public RetInterface {...}

class AInterface
{
  public:
     virtual boost::shared_ptr<RetInterface> get_r() const = 0;
     ...
};

class A1: public AInterface
{
  public:
     boost::shared_ptr<Ret1> get_r() const {...}
     ...
};

此代码无法编译。

在视觉工作室中它提出了

C2555:重写虚函数返回类型不同且不是 协变的

如果我不使用 boost::shared_ptr 但返回原始指针,则代码编译(我知道这是由于 C++ 中的 covariant return types)。我可以看到问题是因为Ret1boost::shared_ptr 不是从RetInterfaceboost::shared_ptr 派生的。但是我想返回Ret1boost::shared_ptr 以供其他类使用,否则我必须在返回后强制转换返回值。

  1. 我做错了吗?
  2. 如果不是,为什么是这样的语言 - 在这种情况下处理智能指针之间的转换应该是可扩展的?是否有理想的解决方法?

【问题讨论】:

  • 如果你不使用 boost::shared_ptr,你会返回指针吗?是托管的 C++ 吗?
  • @Lev 如果我尝试返回原始指针,代码会编译,但是会出现内存管理问题。不,我没有使用托管 C++。
  • 我所做的是:返回原始指针,但记录调用者负责将指针包装在智能指针中,例如std::unique_ptr&lt;Class&gt;(obj.clone()).
  • "我可以看到问题是因为 Ret1 的 boost::shared_ptr 不是从 RetInterface 的 boost::shared_ptr 派生的" 不,它不是

标签: c++ covariance smart-pointers


【解决方案1】:

在 C++ 中重载方法时,您不能更改返回类型(对于非指针、非引用返回类型)。 A1::get_r 必须返回 boost::shared_ptr&lt;RetInterface&gt;

安东尼威廉姆斯有一个很好的综合answer

【讨论】:

    【解决方案2】:

    Mr Fooz 回答了您问题的第 1 部分。第 2 部分,它以这种方式工作,因为编译器不知道它是否会在编译时调用 AInterface::get_r 或 A1::get_r - 它需要知道它将获得什么返回值,因此它坚持使用这两种方法返回相同的类型。这是 C++ 规范的一部分。

    对于解决方法,如果 A1::get_r 返回一个指向 RetInterface 的指针,则 RetInterface 中的虚拟方法仍将按预期工作,并且在销毁指针时将删除正确的对象。不需要不同的返回类型。

    【讨论】:

      【解决方案3】:

      首先,这确实是它在 C++ 中的工作方式:派生类中虚函数的返回类型必须与基类中的相同。有一个特殊的例外,一个返回指向某个类 X 的引用/指针的函数可以被一个返回指向从 X 派生的类的引用/指针的函数覆盖,但是正如您注意到的那样,这不允许 smart 指针(例如shared_ptr),仅用于普通指针。

      如果你的接口RetInterface足够全面,那么你就不需要知道调用代码中实际返回的类型。一般来说,无论如何它都没有意义:get_r 是一个virtual 函数的原因首先是因为您将通过指针或对基类AInterface 的引用来调用它,在这种情况下您可以'不知道派生类会返回什么类型。如果您使用实际的 A1 引用来调用它,您可以在 A1 中创建一个单独的 get_r1 函数来满足您的需要。

      class A1: public AInterface
      {
        public:
           boost::shared_ptr<RetInterface> get_r() const
           {
               return get_r1();
           }
           boost::shared_ptr<Ret1> get_r1() const {...}
           ...
      };
      

      或者,您可以使用访问者模式或类似我的Dynamic Double Dispatch 技术将回调传递给返回的对象,然后该对象可以调用具有正确类型的回调。

      【讨论】:

      • boost::shared_ptr 会自动转换为 boost::shared_ptr 吗?你不需要像 boost::static_pointer_cast 这样的东西吗?
      • 返回指向派生类的指针对于实现多个接口的类的克隆方法是有意义的。
      【解决方案4】:

      也许您可以使用 out 参数来解决“与返回的 boost shared_ptrs 的协方差。

       void get_r_to(boost::shared_ptr<RetInterface>& ) ...
      

      因为我怀疑调用者可以放入更精细的 shared_ptr 类型作为参数。

      【讨论】:

        【解决方案5】:

        这个解决方案怎么样:

        template<typename Derived, typename Base>
        class SharedCovariant : public shared_ptr<Base>
        {
        public:
        
        typedef Base BaseOf;
        
        SharedCovariant(shared_ptr<Base> & container) :
            shared_ptr<Base>(container)
        {
        }
        
        shared_ptr<Derived> operator ->()
        {
            return boost::dynamic_pointer_cast<Derived>(*this);
        }
        };
        

        例如:

        struct A {};
        
        struct B : A {};
        
        struct Test
        {
            shared_ptr<A> get() {return a_; }
        
            shared_ptr<A> a_;
        };
        
        typedef SharedCovariant<B,A> SharedBFromA;
        
        struct TestDerived : Test
        {
            SharedBFromA get() { return a_; }
        };
        

        【讨论】:

          【解决方案6】:

          这是我的尝试:

          template<class T>
          class Child : public T
          {
          public:
              typedef T Parent;
          };
          
          template<typename _T>
          class has_parent
          {
          private:
              typedef char                        One;
              typedef struct { char array[2]; }   Two;
          
              template<typename _C>
              static One test(typename _C::Parent *);
              template<typename _C>
              static Two test(...);
          
          public:
              enum { value = (sizeof(test<_T>(nullptr)) == sizeof(One)) };
          };
          
          class A
          {
          public :
             virtual void print() = 0;
          };
          
          class B : public Child<A>
          {
          public:
             void print() override
             {
                 printf("toto \n");
             }
          };
          
          template<class T, bool hasParent = has_parent<T>::value>
          class ICovariantSharedPtr;
          
          template<class T>
          class ICovariantSharedPtr<T, true> : public ICovariantSharedPtr<typename T::Parent>
          {
          public:
             T * get() override = 0;
          };
          
          template<class T>
          class ICovariantSharedPtr<T, false>
          {
          public:
              virtual T * get() = 0;
          };
          
          template<class T>
          class CovariantSharedPtr : public ICovariantSharedPtr<T>
          {
          public:
              CovariantSharedPtr(){}
          
              CovariantSharedPtr(std::shared_ptr<T> a_ptr) : m_ptr(std::move(a_ptr)){}
          
              T * get() final
             {
                  return m_ptr.get();
             }
          private:
              std::shared_ptr<T> m_ptr;
          };
          

          还有一个小例子:

          class UseA
          {
          public:
              virtual ICovariantSharedPtr<A> & GetPtr() = 0;
          };
          
          class UseB : public UseA
          {
          public:
              CovariantSharedPtr<B> & GetPtr() final
              {
                  return m_ptrB;
              }
          private:
              CovariantSharedPtr<B> m_ptrB = std::make_shared<B>();
          };
          
          int _tmain(int argc, _TCHAR* argv[])
          {
              UseB b;
              UseA & a = b;
              a.GetPtr().get()->print();
          }
          

          解释:

          这个解决方案意味着元编程和修改协变智能指针中使用的类。

          简单的模板结构Child在这里绑定类型Parent和继承。任何从Child&lt;T&gt; 继承的类都将从T 继承并将T 定义为Parent。协变智能指针中使用的类需要定义这种类型。

          has_parent 类用于在编译时检测一个类是否定义了Parent 类型。这部分不是我的,我使用了相同的代码来检测方法是否存在(see here

          由于我们想要与智能指针的协方差,我们希望我们的智能指针模仿现有的类架构。在示例中更容易解释它是如何工作的。

          当一个CovariantSharedPtr&lt;B&gt;被定义时,它继承自ICovariantSharedPtr&lt;B&gt;,它被解释为ICovariantSharedPtr&lt;B, has_parent&lt;B&gt;::value&gt;。由于B 继承自Child&lt;A&gt;has_parent&lt;B&gt;::value 为真,所以ICovariantSharedPtr&lt;B&gt;ICovariantSharedPtr&lt;B, true&gt; 并继承自ICovariantSharedPtr&lt;B::Parent&gt;,即ICovariantSharedPtr&lt;A&gt;。由于A 没有定义Parenthas_parent&lt;A&gt;::value 为假,ICovariantSharedPtr&lt;A&gt;ICovariantSharedPtr&lt;A, false&gt;,并且从无继承。

          重点是Binherits from A,我们有ICovariantSharedPtr&lt;B&gt;inheriting from ICovariantSharedPtr&lt;A&gt;。因此,任何在ICovariantSharedPtr&lt;A&gt; 上返回指针或引用的方法都可以被在ICovariantSharedPtr&lt;B&gt; 上返回相同的方法重载。

          【讨论】:

          • 为那些没有多少时间解读这个问题的人添加一些基本原理和解释怎么样?它与morabot的相比如何?谢谢。
          【解决方案7】:

          this blog post(来自 Raoul Borges)中有一个简洁的解决方案

          在添加对多重继承和抽象方法的支持之前的一段摘录是:

          template <typename Derived, typename Base>
          class clone_inherit<Derived, Base> : public Base
          {
          public:
             std::unique_ptr<Derived> clone() const
             {
                return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
             }
          
          private:
             virtual clone_inherit * clone_impl() const override
             {
                return new Derived(*this);
             }
          };
          
          class concrete: public clone_inherit<concrete, cloneable>
          {
          };
          
          int main()
          {
             std::unique_ptr<concrete> c = std::make_unique<concrete>();
             std::unique_ptr<concrete> cc = b->clone();
          
             cloneable * p = c.get();
             std::unique_ptr<clonable> pp = p->clone();
          }
          

          我鼓励阅读全文。写得简单,解释清楚。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-06-12
            • 1970-01-01
            • 2014-05-30
            • 2018-07-18
            • 2023-03-07
            相关资源
            最近更新 更多