Reputation: 63
I made a factory class for lights in my game where a function "create" creates a unique_ptr and stores it in a STL vector, so I can collect the light data and send it to a shader, then it returns a reference to that unique_ptr in the vector like so:
std::unique_ptr<Light>& create() {
myVector.push_back(std::unique_ptr<Light>(new Light));
return &myVector.back();
}
However, when I add more lights, the unique_ptr reference (not the raw pointer) gets invalidated because it was referencing the unique_ptr object in the vector. So, passing the unique_ptr reference after creating it lead to errors. So, I came up with this solution:
std::unique_ptr<Light> create() {
Light* light = new Light();
myVector.push_back(light);
return std::unique_ptr<Light>(light);
}
Which solved the invalidation issue, but now, when the unique_ptr went out of scope, it replaced its raw pointer in myVector with the next pointer in the vector. So, if I had a vector of pointers: [1, 2, 3] then after unique_ptr went out of scope the vector became [2, 2, 3]. Naturally, I didn't want redundant data so I replaced the vector with a STL set. Now, when the unique_ptr goes out of scope the set finds the redundant data and removes the one copy, so the set correctly contains pointers: [2, 3]. This works and I don't receive any errors, but I don't understand why or how the unique_ptr changes the matching pointer value in the STL container. Is this expected behavior? If so, why/how does it work?
PS: I understand that the purpose of a unique_ptr is to make the pointer owned by one single object. As a result, I prefer having the sole ownership kept in the object that calls the create() function. So, the factory simply creates the light and stores references to the lights it created for easy collection, though the storage done by the factory itself is just a temporary measure. However, I love the nature of the STL set automatically being maintained when the unique_ptr automatically releases itself when it goes out of scope. So, I'd like to keep that generic design, only if it isn't some fluke with the unique_ptr.
Upvotes: -1
Views: 739
Reputation: 1321
Smart pointers are about solving the resource ownership problem. This doesn't mean you always use shared_ptr and never use "raw" pointers.
It appears that you want to have a vector of lights where this vector is responsible for the resources. For that you don't even need pointers because the vector handles the pointers internally.
vector<Light> myVector;
You can get a handle to this resource from the vector or from the return of this create function.
const Light& create() {
Light light;
myVector.push_back(light);
return myVector.back();
}
What is the consumer of this resource allowed to do? Can it delete the light?
And what guarantees can the vector make to the consumers? Will these pointers be used passed the lifetime of the list.
Upvotes: 0
Reputation: 153840
Since the std::vector<std::unique_ptr<Light>>
is the owner of the Light
s, there is little use in returning references to these rather than pointers to the Light
s. The references are only useful if you need to replace the value. I'd just travel in terms of Light*
or Light const*
where the objects are used rather than owned. You can get the pointer held by a std::unique_ptr<T>
with the get()
method.
Instead of returning a Light*
you could return a simple_ptr<Light>
where simple_ptr<T>
essentially just has a constructor taking a T*
and operators operator->()
which returns a T*
and operator*()
which returns a T&
:
template <typename T>
class simple_ptr {
T* ptr;
public:
explicit simple_ptr(T* ptr): ptr(ptr) {}
T* operator->() const { return this->ptr; }
T& operator*() const { return *this->ptr; }
};
The generated functions just do the Right Thing (i.e. copy the pointer as needed). Without jumping through hoops it wouldn't be possible delete
this pointer. If you give your Light
type a custom unary operator&()
you could make it even harder to explicitly get to the pointer. Tt won't be impossible, though, as you can always call ptr.operator->()
to get it. However, you should only defend against Murphy, not against Machiavelli.
Upvotes: 2
Reputation: 63775
You're contradicting the design of std::unique_ptr
by trying to have multiple copies of it.
This could be solved by using std::shared_ptr
instead.
std::shared_ptr<Light> create() {
myVector.push_back(std::make_shared<Light>());
return myVector.back();
}
Or if you want your vector to be primarily responsible for the lifetime of these lights, return a std::weak_ptr
, which knows when it's dangling.
std::weak_ptr<Light> create() {
myVector.push_back(std::make_shared<Light>());
return myVector.back();
}
Upvotes: 0
Reputation: 9343
Since you don't want to delete the object while either the caller of your factory function or the vector has a pointer to it, why not use an std::shared_ptr
?
Upvotes: 0