Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
Interface
of which there are Foo
s and Bar
s;Scene
which owns these objects;Foo
s are to be unique_ptr
s and Bar
s are to be shared_ptr
s in my main (for reasons explained in the previous question);main
passes them to the Scene
instance, which takes ownership.Minimal code example is 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); }
Uncommenting the commented lines results in:
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) ^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr
s).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
yes, it's an RAII class.
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter > shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E > unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointerb)
U
is not an array typec) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of 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{}); } };
Full compilable example is here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With