web-dev-qa-db-ja.com

スマートポインタで共変リターン型を使用するにはどうすればよいですか?

私はこのようなコードを持っています:

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

このコードはコンパイルされません。

VisualStudioで発生します

C2555:オーバーライドする仮想関数の戻り値の型が異なり、共変ではありません

boost::shared_ptrを使用せずに生のポインターを返す場合、コードはコンパイルされます(これは、C++では 共変リターンタイプ が原因であると理解しています)。問題は、boost::shared_ptrRet1RetInterfaceboost::shared_ptrから派生していないためであることがわかります。しかし、他のクラスで使用するためにboost::shared_ptrRet1を返したいのですが、そうでない場合は、返された後に戻り値をキャストする必要があります。

  1. 私は何か間違ったことをしていますか?
  2. そうでない場合、なぜこのような言語なのですか?このシナリオでは、スマートポインター間の変換を処理するために拡張可能である必要がありますか?望ましい回避策はありますか?
62
amit

まず、これは実際にC++でどのように機能するかです。派生クラスの仮想関数の戻り値の型は、基本クラスと同じである必要があります。あるクラスXへの参照/ポインターを返す関数は、Xから派生したクラスへの参照/ポインターを返す関数によってオーバーライドできるという特別な例外がありますが、ご存知のとおり、これではsmartポインタ(shared_ptrなど)、プレーンポインタ専用。

インターフェイスRetInterfaceが十分に包括的である場合、呼び出し元のコードで実際に返される型を知る必要はありません。一般に、とにかく意味がありません。そもそもget_rvirtual関数である理由は、基本クラス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 {...}
     ...
};

または、ビジターパターンまたはmy Dynamic Double Dispatch テクニックのようなものを使用して、返されたオブジェクトにコールバックを渡し、正しいタイプでコールバックを呼び出すことができます。

23

これが私の試みです:

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>として解釈されます。 BChild<A>から継承するため、has_parent<B>::valueはtrueです。したがって、ICovariantSharedPtr<B>ICovariantSharedPtr<B, true>であり、ICovariantSharedPtr<B::Parent>から継承します。これはICovariantSharedPtr<A>です。 。 AにはParentが定義されていないため、has_parent<A>::valueはfalse、ICovariantSharedPtr<A>ICovariantSharedPtr<A, false>であり、何も継承しません。

重要な点は、BAから継承しているため、ICovariantSharedPtr<B>ICovariantSharedPtr<A>から継承していることです。したがって、ICovariantSharedPtr<A>でポインタまたは参照を返すメソッドは、ICovariantSharedPtr<B>で同じものを返すメソッドによってオーバーロードされる可能性があります。

1
Grégory MALLET

C++でメソッドをオーバーロードする場合、戻り値の型(非ポインター、非参照の戻り値の型の場合)を変更することはできません。 A1::get_rboost::shared_ptr<RetInterface>を返す必要があります。

アンソニーウィリアムズは素晴らしい包括的な 答え を持っています。

1
Mr Fooz

このソリューションはどうですか?

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

このブログ投稿 (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
Bruce Adams