78

次のようなコードがあります。

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++ の共変の戻り値の型によるものだと理解しています)。問題は、 が から派生していないためであるboost::shared_ptrことがわかります。しかし、他のクラスで使用するために を返したいのですが、そうでない場合は、戻り値を戻り値の後にキャストする必要があります。 Ret1boost::shared_ptrRetInterfaceboost::shared_ptrRet1

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

7 に答える 7

31

まず、これは実際に C++ でどのように機能するかです。派生クラスの仮想関数の戻り値の型は、基本クラスと同じでなければなりません。一部のクラス X への参照/ポインターを返す関数は、X から派生したクラスへの参照/ポインターを返す関数によってオーバーライドできるという特別な例外がありますが、これはスマートポインターを許可しないことに注意してください。 ( など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 {...}
     ...
};

または、ビジター パターンまたはダイナミック ダブル ディスパッチテクニックのようなものを使用して、返されたオブジェクトにコールバックを渡し、正しい型でコールバックを呼び出すことができます。

于 2008-10-13T09:58:40.397 に答える
4

このブログ投稿(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 = c->clone();
 
   cloneable * p = c.get();
   std::unique_ptr<clonable> pp = p->clone();
}

記事全体を読むことをお勧めします。簡単に書かれており、よく説明されています。

于 2019-06-11T11:24:38.420 に答える
2

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

Anthony Williams は、すばらしい包括的な回答を提供しています。

于 2008-10-13T04:49:45.013 に答える
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。この部分は私のものではありません。メソッドが存在するかどうかを検出するのと同じコードを使用しました (こちらを参照) 。

スマート ポインターとの共分散が必要なため、スマート ポインターは既存のクラス アーキテクチャを模倣する必要があります。例でどのように機能するかを説明する方が簡単です。

aCovariantSharedPtr<B>が定義されると、 から継承されICovariantSharedPtr<B>、 として解釈されICovariantSharedPtr<B, has_parent<B>::value>ます。As Binherits from Child<A>, has_parent<B>::valueis true, so ICovariantSharedPtr<B>is ICovariantSharedPtr<B, true>and inherits from ICovariantSharedPtr<B::Parent>which is ICovariantSharedPtr<A>. As Ahas no Parentdefined, has_parent<A>::valueis false, ICovariantSharedPtr<A>is ICovariantSharedPtr<A, false>and inherits from nothing.

要点はBから継承しAているように、からICovariantSharedPtr<B>継承していICovariantSharedPtr<A>ます。したがって、ポインタまたは参照をICovariantSharedPtr<A>返すメソッドは、同じ on を返すメソッドによってオーバーロードできますICovariantSharedPtr<B>

于 2015-09-11T17:27:38.367 に答える
-1

Fooz 氏が質問のパート 1 に回答しました。パート 2 では、コンパイル時に AInterface::get_r と A1::get_r のどちらを呼び出すかをコンパイラが認識していないため、このように動作します。取得する戻り値を知る必要があるため、両方のメソッドを要求します。同じ型を返します。これは C++ 仕様の一部です。

回避策として、A1::get_r が RetInterface へのポインターを返す場合、RetInterface の仮想メソッドは引き続き期待どおりに機能し、ポインターが破棄されると適切なオブジェクトが削除されます。異なる戻り値の型は必要ありません。

于 2008-10-13T05:00:11.043 に答える
-2

おそらく、outパラメータを使用して、返されるブーストshared_ptrsとの共分散を回避できます。

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

呼び出し元が引数としてより洗練されたshared_ptrタイプをドロップできると思うので。

于 2009-06-09T13:26:04.450 に答える