unique_ptr

Started by
7 comments, last by Codarki 10 years, 7 months ago
class Foo
{
private:
	int x,y,z;

public:
	Foo() : x(0), y(0), z(10){}
	Foo(int i) : x(i), y(0), z(0){}
	~Foo()
	{
		std::cout << x << " Foo~()\n";
	}
	void Print()
	{
		std::cout << x << "," << y << "," << z << std::endl;
	}
};

int main(int argc, const char* argv[])
{
	{
		std::vector vFoo;
		for(int i = 0; i < 10; ++i)			
		{
			vFoo.push_back(std::unique_ptr(new Foo(i)));
			vFoo.back()->Print();
		}
		std::unique_ptr p;
		p = std::move(vFoo.front());
		std::cout << std::endl;
		p->Print();
		std::cout << std::endl;

	}
	return 0;
}
I am not following here, I read that when you use = or std::move that the unique_ptr would delete the object? in my code above the only time I see the deconstructor called on Foo() is when I leave scope? Please educate me on what I am doing wrong, or not understanding. Thanks!
Advertisement

unique_ptr owns the object that it points to. move() can be used to transfer that ownership to another unique_ptr. You will find that after your call to move(), vFoo.front() will be a null unique_ptr; ownership of the object has been transferred to p using its move-assignment operator.

Firstly, you can'd use template classes without specifying the template parameters. That applies to both unique_ptr and vector.

Secondly no, moving a unique pointer does not delete the object. What it does do is zero out the object moved from, such that there is always only one pointer to that object. More generally objects with move capability will take the guts out of the object moved from and put it in the object moved to. Passing a function an rvalue(temporary), using std::move with any value, or using std::forward with a forwarded rvalue will all allow a unique_ptr, or any other type that supports it, to be moved.

I am not sure what I am doing wrong but I can't figure out how to get this code to work for a templated resource manager with a std::map and std::unique_ptr... or should be using a std::shared_ptd...


namespace NX
{
class BaseGameObject
{
protected:
    BaseGameObject(){}

public:
    virtual ~BaseGameObject(){}
    virtual void Run() = 0;
};
class BaseResourceObject
{
protected:
    std::string id;
    BaseResourceObject(){}

public:
    virtual ~BaseResourceObject(){}
    virtual void Run() = 0;
    virtual bool Load(const std::string& s) = 0;
    const std::string& GetId() const
    {
        return id;
    }
};
template<typename T>
class Manager
{
protected:
    std::map<std::string, std::unique_ptr<T>> resources;

public:
    Manager(){}
    ~Manager(){}
    virtual void Run() = 0;
    virtual bool Insert(std::string s, std::unique_ptr<T> p) = 0;
    const T& GetResource(const std::string& s) const
    {
        return resource.find(s);        
    }
};
}

class Foo : public NX::BaseGameObject
{
private:
    int x,y,z;

public:
    Foo() : NX::BaseGameObject(), x(0), y(0), z(10){}
    Foo(int i) : x(i), y(0), z(0){}
    virtual ~Foo()
    {
        std::cout << x << " Foo~()\n";
    }
    virtual void Run()
    {
        std::cout << "RUN\n";
        Print();
    }
    void Print()
    {
        std::cout << x << "," << y << "," << z << std::endl;
    }
};


int main(int argc, const char* argv[])
{
    //NX::ManagerGameObjects mgo;
    //mgo.Insert("TEST", std::unique_ptr<Foo>(new Foo(89)));

Thanks!

What exactly is your problem? Compile error? Linker error? Runtime error?

std::map::find() returns an iterator, not a const T&. You can indirect the iterator to get the std::pair for the key/value pair the iterator points to, then get the value with second, and dereference this value to get the actual object. ie, auto iter=resource.find(s); return *((*iter).second);. This ignores the case where find returns resources.end(), meaning it wasn't found, in which case you throw an exception trying to dereference iter.

A common idiom used in this situation is to store a weak_ptr in the resource map, and return shared_ptr from GetResource(). GetResource will search the map and if an entry is found, test the weak_ptr stored. If this weak_ptr is valid then it will create a shared_ptr from it and return. If this weak_ptr is not valid (meaning all existing shared_ptrs have gone out of scope) then the resource is reloaded into a shared_ptr, a new weak_ptr is created and stored in the map and the shared_ptr is returned. If no entry at all is found, then a new entry is created, a new shared_ptr made, a weak_ptr stashed in the map, and return.

The point of this is that you aren't handing out possibly unsafe references to an object held by a unique_ptr, when you can't guarantee that the unique_ptr isn't going to go away at some point. Returning the reference just means that you have a potentially dangling reference if/when you wipe your resource map. By storing handed-out pointers as shared_ptrs, then the objects that request these resources have a say in managing the lifetime of the resource object. That is, the resource object can't disappear without everyone who has a pointer to the resource dropping it. Using a weak_ptr in the map means that if all shared_ptrs have been dropped, then the map itself won't stubbornly try to hold onto the resource, since a weak_ptr can't keep it alive.

I read that when you use = or std::move that the unique_ptr would delete the object?

Operator = on unique pointer is forbidden, and std::move won't delete it. Also you'r exaple has unique_ptr parameter as value, which shouldn't compile (not copy-constructible)

I am not sure what I am doing wrong but I can't figure out how to get this code to work for a templated resource manager with a std::map and std::unique_ptr... or should be using a std::shared_ptd...

...

Thanks!

I'm pretty sure you should use shared_ptr for resource construction. FleBlanc describes good when manager shouldn't keep resources alive, with weak_ptr.

Resource from unique_ptr is not meant to be shared. Unless if the lifetime stucture is very clear, and the destruction and access patterns are well defined.

Operator = on unique pointer is forbidden, and std::move won't delete it. Also you'r exaple has unique_ptr parameter as value, which shouldn't compile (not copy-constructible)

std::unique_ptr does have an operator= overload. You can use either a nullptr_t or a std::unique_ptr rvalue as the right hand side. Similarly, it is legal to have a function that takes a std::unique_ptr by value. You'd need to call that function with a std::unique_ptr or std::auto_ptr rvalue or a nullptr_t.
std::unique_ptr does have an operator= overload. You can use either a nullptr_t or a std::unique_ptr rvalue as the right hand side. Similarly, it is legal to have a function that takes a std::unique_ptr by value. You'd need to call that function with a std::unique_ptr or std::auto_ptr rvalue or a nullptr_t.

You're right. I wasn't specific enough and was thinking only about lvalues as seen in first example here.

EDIT: Ok I'll try to be a bit constructive.

Example1:

You're right, destructors for std::vector and std::unique_ptr p are called when leaving from scope. (std::unique_ptr needs a type btw). You are moving first element of the vector to the variable p. Your Foo type is still destructible after being moved-from. You should see 11 messages. This means you're constucting 10 Foo objects and destructing 11 of them.

Example2:

BaseResourceObject is not used at all. I don't think Manager should have Run() virtual function. Manager is not a concrete class, I can't see the Insert() implementation. GetResource() method accesses unknown member.

I've used similar classes alot. I've named the classes something like asset_cache, what you call Manager. It doesn't need to be virtual. It's ok to store resource as unique_ptr and return just a reference, just make sure the Manager is destructed after all other objects who have access to the reference.

This topic is closed to new replies.

Advertisement