如何将协变返回类型与智能指针一起使用?


70

我有这样的代码:

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 {...}
     ...
};

此代码无法编译。

在Visual Studio中,它引发了

C2555:覆盖虚拟函数的返回类型不同并且不是协变的

如果我不使用boost::shared_ptr而是返回原始指针,则代码会编译(我理解这是由于C ++中的协变返回类型所致)。我可以看到这个问题是因为boost::shared_ptrRet1不是源自boost::shared_ptrRetInterface。但是我想返回boost::shared_ptrofRet1以用于其他类,否则我必须在返回之后强制转换返回的值。

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

如果您不使用boost :: shared_ptr,您是否返回指针?它是托管C ++吗?
列夫

@Lev如果我尝试返回原始指针,则代码会编译,但是会出现内存管理问题。不,我没有使用托管C ++。
amit

我要做的是:返回原始指针,但记录调用者负责将指针包装在智能指针中的情况,例如std::unique_ptr<Class>(obj.clone())
quant_dev

我可以看到问题是因为Ret1的boost :: shared_ptr不是从RetInterface的boost :: shared_ptr派生的“不,不是
curiousguy

Answers:


25

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

如果您的界面RetInterface足够全面,那么您将不需要知道调用代码中的实际返回类型。总的来说,这是毫无意义的:首先是因为它get_r是一个virtual函数,因为您将通过对基类的指针或引用来调用它AInterface,在这种情况下,您将无法知道派生类将使用哪种类型返回。如果使用实际A1参考来调用它,则可以仅创建一个满足您需要的单独get_r1函数A1

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

另外,您可以使用访问者模式或类似我的动态双调度技术的方法将回调传递给返回的对象,然后该对象可以使用正确的类型调用该回调。


boost :: shared_ptr <Ret1>是否会自动转换为boost :: shared_ptr <RetInterface>?您是否至少不需要boost :: static_pointer_cast之类的东西?
h9uest

7
对于实现多个接口的类,使用克隆方法返回指向派生类的指针是有意义的。
quant_dev

2

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

安东尼·威廉姆斯(Anthony Williams)有一个很好的综合答案


2

此博客文章中发布了一种简洁的解决方案(来自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();
}

我鼓励阅读全文。它简单地编写并得到了很好的解释。


1

那这个解决方案呢:

例如:


1

这是我的尝试:

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<T>继承的任何类都将从继承T并定义TParent。协变智能指针中使用的类需要定义此类型。

该类has_parent用于在编译时检测是否定义了类型Parent。这部分不是我的,我使用了与检测方法是否存在相同的代码(请参阅此处

当我们希望与智能指针协方差时,我们希望我们的智能指针模仿现有的类体系结构。在示例中更容易解释它是如何工作的。

当一个CovariantSharedPtr<B>被定义,它继承ICovariantSharedPtr<B>,这被解释为ICovariantSharedPtr<B, has_parent<B>::value>。正如B所继承Child<A>has_parent<B>::value是真实的,所以ICovariantSharedPtr<B>ICovariantSharedPtr<B, true>和继承ICovariantSharedPtr<B::Parent>ICovariantSharedPtr<A>。正如A没有Parent定义一样,它has_parent<A>::value是假的,是假ICovariantSharedPtr<A>ICovariantSharedPtr<A, false>并且是从无中继承的。

主要要点是B继承自A,我们ICovariantSharedPtr<B>继承自ICovariantSharedPtr<A>。因此,返回指针或引用on的任何方法ICovariantSharedPtr<A>都可以通过返回on的方法而重载ICovariantSharedPtr<B>


为那些没有时间了解这些的人增加一些理由和解释呢?它与morabot的相比如何?谢谢
石英

-1

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

对于变通方法,如果A1 :: get_r返回指向RetInterface的指针,则RetInterface中的虚拟方法仍将按预期工作,并且在销毁指针时将删除适当的对象。无需其他返回类型。


-2

也许您可以使用out参数来避免“与返回的boost shared_ptrs相关的协方差。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.