如何使用带有智能指针的协变返回类型?

时间:2008-10-13 04:42:58

标签: c++ covariance smart-pointers

我有这样的代码:

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)。我可以看到问题是因为boost::shared_ptr的{​​{1}}不是来自Ret1的{​​{1}}。但是我希望返回boost::shared_ptr RetInterface以便在其他类中使用,否则我必须在返回后转换返回的值。

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

7 个答案:

答案 0 :(得分:23)

首先,这确实是它在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 {...}
     ...
};

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

答案 1 :(得分:1)

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

Anthony Williams有一个很好的综合answer

答案 2 :(得分:1)

这个解决方案怎么样:

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

e.g:

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

答案 3 :(得分: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();
}

解释:

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

简单模板struct Child用于绑定类型Parent和继承。从Child<T>继承的任何类都将继承自T并将T定义为Parent。协变智能指针中使用的类需要定义这种类型。

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

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

定义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未定义Parenthas_parent<A>::value为false,ICovariantSharedPtr<A>ICovariantSharedPtr<A, false>并且从无任何继承。

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

答案 4 :(得分:0)

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

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

SELECT MIN(id) id, 
CASE WHEN COUNT(DISTINCT firstname) > 1 THEN 'MIXED_VALUE' ELSE MIN(firstname) END firstname,
CASE WHEN COUNT(DISTINCT lastname) > 1 THEN 'MIXED_VALUE' ELSE MIN(lastname) END lastname,
price_id, service_id,
CASE WHEN COUNT(DISTINCT product_sku) > 1 THEN 'MIXED_VALUE' ELSE MIN(product_sku) END product_sku
FROM yourTable
GROUP BY price_id, service_id

我鼓励阅读全文。它的文字写得很简单,解释也很清楚。

答案 5 :(得分:-1)

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

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

答案 6 :(得分:-1)

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

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

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

相关问题