簡體   English   中英

如何將協變返回類型與智能指針一起使用?

[英]How can I use covariant return types with 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++ 中的協變返回類型)。 我可以看到問題是因為Ret1 boost::shared_ptr不是從RetInterface boost::shared_ptr RetInterface 但是我想返回Ret1 boost::shared_ptr以用於其他類,否則我必須在返回后轉換返回的值。

  1. 難道我做錯了什么?
  2. 如果不是,為什么語言是這樣 - 在這種情況下處理智能指針之間的轉換應該是可擴展的? 是否有理想的解決方法?

首先,這確實是它在 C++ 中的工作方式:派生類中虛函數的返回類型必須與基類中的相同。 有一個特殊的例外,即返回指向某個類 X 的引用/指針的函數可以被返回指向從 X 派生的類的引用/指針的函數覆蓋,但正如您所注意到的,這不允許使用智能指針(例如shared_ptr ),僅用於普通指針。

如果您的接口RetInterface足夠全面,那么您將不需要知道調用代碼中的實際返回類型。 一般來說,無論如何它沒有意義: get_rvirtual函數的原因是因為您將通過指針或對基類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 {...}
     ...
};

或者,您可以使用訪問者模式或類似我的動態雙調度技術的東西將回調傳遞給返回的對象,然后該對象可以使用正確的類型調用回調。

這篇博文(來自 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();
}

我鼓勵閱讀全文。 它寫得簡單,解釋得很好。

在 C++ 中重載方法時,您不能更改返回類型(對於非指針、非引用返回類型)。 A1::get_r必須返回boost::shared_ptr<RetInterface>

安東尼威廉姆斯有一個很好的綜合答案

這個解決方案怎么樣:

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

說明:

該解決方案意味着元編程並修改協變智能指針中使用的類。

簡單的模板 struct 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> 由於A沒有定義Parenthas_parent<A>::value為 false, ICovariantSharedPtr<A>ICovariantSharedPtr<A, false>並且從無繼承。

重點是B繼承自A ,我們有ICovariantSharedPtr<B>繼承自ICovariantSharedPtr<A> 因此,任何在ICovariantSharedPtr<A>上返回指針或引用的方法都可以通過在ICovariantSharedPtr<B>上返回相同內容的方法重載。

福茲先生回答了您問題的第 1 部分。 第 2 部分,它以這種方式工作,因為編譯器不知道它在編譯時是調用 AInterface::get_r 還是 A1::get_r - 它需要知道它將獲得什么返回值,因此它堅持使用這兩種方法返回相同的類型。 這是 C++ 規范的一部分。

對於變通方法,如果 A1::get_r 返回一個指向 RetInterface 的指針,則 RetInterface 中的虛擬方法仍將按預期工作,並且在銷毀指針時將刪除正確的對象。 不需要不同的返回類型。

也許您可以使用 out 參數來解決“與返回的 boost shared_ptrs 的協方差”。

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

因為我懷疑調用者可以將更精細的 shared_ptr 類型作為參數放入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM