前の質問 の回答からヒントを得た後、コードをコーディングすると、Scene :: addObjectのオーバーロードに関する問題が発生しました。
関連するビットを繰り返し、この詳細を可能な限り最小限に抑えて自己完結させるには:
Interface
から継承するオブジェクトの階層があり、Foo
sとBar
sがあります。Scene
があります。Foo
sはunique_ptr
sであり、Bar
sはshared_ptr
sです(前の質問で説明した理由により)。main
は、所有権を取得するScene
インスタンスにそれらを渡します。最小限のコード例は this :
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
コメント行のコメントを外すと、次の結果になります。
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
共有のコメントを外し、ユニークなものをコメントすることもコンパイルするので、コンパイラーが言うように、問題はオーバーロードにあると思います。ただし、これらの型はいずれも何らかのコレクションに格納する必要があるため、オーバーロードが必要であり、実際にベースへのポインターとして保持されます(おそらくすべてがshared_ptr
sに移動されます)。
Scene
の所有権を取得している(およびshared_ptr
sの参照カウンターを増やしている)ことを明確にしたいので、両方の値を渡します。問題がどこにあるのか私には本当にはっきりしていません。他の場所でこの例を見つけることができませんでした。
あなたが遭遇している問題は、このコンストラクタ _shared_ptr
_(13) (明示的ではありません)のコンストラクタです。 _unique_ptr
_(6) (明示的でもありません)。
_template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
_
13)
r
によって現在管理されているオブジェクトを管理する_shared_ptr
_を構築します。r
に関連付けられた削除者は、管理対象オブジェクトの将来の削除のために保存されます。r
は、呼び出し後にオブジェクトを管理しません。_
std::unique_ptr<Y, Deleter>::pointer
_が_T*
_と互換性がない場合、このオーバーロードはオーバーロード解決に関与しません。r.get()
がNULLポインターの場合、このオーバーロードはデフォルトのコンストラクター(1)と同等です。 (C++ 17以降)
_template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
_
6)所有権を
u
から_unique_ptr
_に転送することで_*this
_を構築します。ここで、u
は指定された削除者(E)で構築されます。このコンストラクタは、次のすべてに該当する場合にのみオーバーロード解決に関与します。
a)_
unique_ptr<U, E>::pointer
_は暗黙的にポインターに変換可能b)
U
は配列型ではありませんc)
Deleter
が参照型でE
がD
と同じ型であるか、Deleter
が参照型ではなくE
がD
に暗黙的に変換可能
非ポリモーフィックの場合、非テンプレート移動コンストラクターを使用する_unique_ptr<T>
_から_unique_ptr<T>&&
_を構築しています。オーバーロード解決は非テンプレートを優先します
Scene
は_shared_ptr<Interface>
_ sを格納すると仮定します。その場合、_unique_ptr
_に対してaddObject
をオーバーロードする必要はありません。呼び出しで暗黙的な変換を許可することができます。
もう1つの答えは、あいまいさと可能な解決策を説明しています。両方のオーバーロードが必要になった場合の別の方法を次に示します。このような場合はいつでも別のパラメーターを追加して、あいまいさを解消し、タグディスパッチを使用できます。ボイラープレートコードはScene
のプライベート部分に隠されています:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
完全にコンパイル可能な例は here。 です
2つのオーバーロードを宣言しました。1つはstd::unique_ptr<Interface>
を受け取り、もう1つはstd::shared_ptr<Interface>
を受け取りますが、std::unique_ptr<Foo>
型のパラメーターを渡します。どの関数も直接一致しないため、コンパイラは関数を呼び出すために変換を実行する必要があります。
std::unique_ptr<Interface>
(基本クラスへの一意のポインターへの単純な型変換)とstd::shared_ptr<Interface>
(基本クラスへの共有ポインターへの変更)に使用できる1つの変換があります。これらの変換は同じ優先度を持っているため、使用する変換がコンパイラーにわからないため、関数があいまいになります。
std::unique_ptr<Interface>
またはstd::shared_ptr<Interface>
を渡す場合、変換は必要ないため、あいまいさはありません。
解決策は、単にunique_ptr
オーバーロードを削除し、常にshared_ptr
に変換することです。これは、2つのオーバーロードが同じ振る舞いをしていると仮定しています。メソッドの名前を変更しない方が適切な場合があります。
Jrokによる解決策はすでに非常に優れています。次のアプローチにより、コードをさらに適切に再利用できます。
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "unique\n"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "shared\n"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
ここで行うことは、最初にスマートポインターを再バインドできるヘルパークラスをいくつか導入することです。
メインのFoosはunique_ptrs、Barsはshared_ptrsです(前の質問で説明した理由により)。
Foo
へのポインターの代わりにBar
へのポインターと_Interface
へのポインターの観点でオーバーロードできますか?