スマートポインターで共変の戻り値型を使用するにはどうすればよいですか?
-
10-07-2019 - |
質問
次のようなコードがあります:
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 {...}
...
};
このコードはコンパイルされません。
Visual Studioで発生します
C2555:オーバーライドする仮想関数の戻り値の型は異なります 共変
boost::shared_ptr
を使用せず、生のポインターを返す場合、コードはコンパイルされます(これは共変の戻り値の型)。問題は、Ret1
of RetInterface
が<=> of <=>から派生していないためです。しかし、他のクラスで使用するために<=>の<=>を返したい場合は、戻り後に戻り値をキャストする必要があります。
- 何か間違ったことをしていますか?
- そうでない場合、なぜこのような言語なのか-このシナリオでスマートポインター間の変換を処理するために拡張可能である必要がありますか?望ましい回避策はありますか?
解決
まず、これは実際にC ++でどのように機能するかです。派生クラスの仮想関数の戻り値の型は、基本クラスと同じでなければなりません。あるクラスXに参照/ポインターを返す関数は、Xから派生するクラスに参照/ポインターを返す関数でオーバーライドできるという特別な例外がありますが、ご注意のとおり、これはスマートポインター(shared_ptr
など)、単なるポインター用。
インターフェイスRetInterface
が十分に包括的な場合、呼び出しコードで実際に返される型を知る必要はありません。一般に、とにかく意味がありません:理由get_r
が最初にvirtual
関数である理由は、ベースクラスAInterface
へのポインターまたは参照を介して呼び出すためです。派生クラスが返すタイプを知りません。実際の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 {...}
...
};
別の方法として、ビジターパターンまたは私のダイナミックダブルディスパッチのようなテクニックを使用できます。返されたオブジェクトにコールバックを渡すと、正しいタイプでコールバックを呼び出すことができます。
他のヒント
このソリューションはどうですか:
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_; }
};
C ++でメソッドをオーバーロードする場合、戻り値の型(非ポインター、非参照の戻り値の型)は変更できません。 A1::get_r
はboost::shared_ptr<RetInterface>
を返す必要があります。
Anthony Williamsには、総合的な回答があります。
これが私の試みです:
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
を継承し、has_parent
をCovariantSharedPtr<B>
として定義します。共変スマートポインターで使用されるクラスでは、この型を定義する必要があります。
クラス このブログ投稿(Raoul Borgesより) 複数の継承と抽象メソッドのサポートを追加する前のビットの抜粋は次のとおりです。 記事全体を読むことをお勧めします。簡単に書かれており、よく説明されています。ICovariantSharedPtr<B>
は、クラスがタイプICovariantSharedPtr<B, has_parent<B>::value>
を定義しているかどうかをコンパイル時に検出するために使用されます。この部分は私のものではありません。メソッドが存在するかどうかを検出するのと同じコードを使用しました(
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();
}
Mr Fooz が質問のパート1に回答しました。パート2、この方法で動作するのは、コンパイル時にAInterface :: get_rまたはA1 :: get_rを呼び出すかどうかをコンパイラが認識しないためです。同じ型を返します。これはC ++仕様の一部です。
回避策として、A1 :: get_rがRetInterfaceへのポインターを返す場合、RetInterfaceの仮想メソッドは期待どおりに機能し、ポインターが破棄されると適切なオブジェクトが削除されます。異なる戻り値の種類は必要ありません。
出力パラメータを使用して、返されたboost shared_ptrsとの共分散を回避することができます。
void get_r_to(boost::shared_ptr<RetInterface>& ) ...
発信者はより洗練されたshared_ptr型を引数としてドロップできると思うので