如何在智能指针中使用协变返回类型?

我有这样的代码:

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 + + 中的 协变返回类型协变返回类型)。我可以看出问题是因为 Ret1boost::shared_ptr不是从 RetInterfaceboost::shared_ptr派生出来的。但是我想返回 Ret1boost::shared_ptr,以便在其他类中使用,否则我必须在返回之后强制转换返回的值。

  1. 我做错什么了吗?
  2. 如果没有,为什么语言是这样的-它应该是可扩展的,以处理智能指针之间的转换在这种情况下?有没有可取的解决办法?
18897 次浏览

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

安东尼威廉姆斯有一个不错的综合 回答

首先,这确实是 C + + 中的工作方式: 派生类中虚函数的返回类型必须与基类中的返回类型相同。有一个特殊的例外,一个函数返回一个引用/指针到某个类 X 可以被一个函数覆盖,这个函数返回一个引用/指针到一个从 X 派生的类,但是正如你注意到的,这不允许 聪明指针(如 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 {...}
...
};

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

也许您可以使用一个 out 参数来绕过“与返回的 boostshare _ ptrs 的协方差”。

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

因为我怀疑调用者可以放入一个更精炼的 share _ ptr 类型作为参数。

这个解决方案怎么样:

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_; }
};

下面是我的尝试:

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继承,并将 T定义为 Parent。协变智能指针中使用的类需要定义这种类型。

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>。因为 ICovariantSharedPtr<B>0没有定义 ICovariantSharedPtr<B>1,所以 ICovariantSharedPtr<B>2是假的,ICovariantSharedPtr<A>ICovariantSharedPtr<B>4,从零开始继承。

主要的一点是 BA继承,我们有 ICovariantSharedPtr<B>ICovariantSharedPtr<A>继承。因此,在 ICovariantSharedPtr<A>上返回指针或引用的任何方法都可以被在 ICovariantSharedPtr<B>上返回相同的方法重载。

有一个简洁的解决方案张贴在 这篇博文(从拉乌尔博尔赫斯)

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

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 = c->clone();
 

cloneable * p = c.get();
std::unique_ptr<clonable> pp = p->clone();
}

我鼓励读完整的文章。它写得很简单,解释也很好。