What does it mean to have sole ownership of object for unique_ptr?
Ownership is all about: who cleans up the resource when it is no longer needed?
To be the only owner of a resource means that only you are responsible for deleting the object when it is no longer needed. This implies that nobody else can use the object after your lifetime ended. If others depend on the object still being alive after your lifetime ended you need shared ownership.
In modern C++, raw pointers should not participate in ownership. When you pass a raw pointer to a function you expect that this function will not hold on to this pointer to use it later.
What does owning mean in the context of programming? [duplicate]
You can own a resource, i.e. anything that there is a limited amount of. This is usually memory or system handles. Whatever owns the resource is responsible for releasing it when done using it.
std::unique_ptr
and std::shared_ptr
are examples of an owning wrapper. It releases their memory when it goes out of use. Same goes for any other RAII class.
std::basic_string_view
is non-owning, which is a nice way of saying that it is not bound to actual lifetime of the string in any way, and that, if you are not careful, it may dangle if the string reallocates.
How does Rust know which types own resources?
tl;dr: "owning" types in Rust are not some magic and they are most certainly not hardcoded into the compiler or language. They are just types which written in a certain way (do not implement Copy
and likely have a destructor) and have certain semantics which is enforced through non-copyability and the destructor.
In its core Rust's ownership mechanism is very simple and has very simple rules.
First of all, let's define what move is. It is simple - a value is said to be moved when it becomes available under a new name and stops being available under the old name:
struct X(u32);
let x1 = X(12);
let x2 = x1;
// x1 is no longer accessible here, trying to use it will cause a compiler error
Same thing happens when you pass a value into a function:
fn do_something(x: X) {}
let x1 = X(12);
do_something(x1);
// x1 is no longer accessible here
Note that there is absolutely no magic here - it is just that by default every value of every type behaves like in the above examples. Values of each struct or enum you or someone else creates by default will be moved.
Another important thing is that you can give every type a destructor, that is, a piece of code which is invoked when the value of this type goes out of scope and destroyed. For example, destructors associated with Vec
or Box
will free the corresponding piece of memory. Destructors can be declared by implementing Drop
trait:
struct X(u32);
impl Drop for X {
fn drop(&mut self) {
println!("Dropping {}", x.0);
}
}
{
let x1 = X(12);
} // x1 is dropped here, and "Dropping 12" will be printed
There is a way to opt-out of non-copyability by implementing Copy
trait which marks the type as automatically copyable - its values will no longer be moved but copied:
#[derive(Copy, Clone)] struct X(u32);
let x1 = X(12);
let x2 = x1;
// x1 is still available here
The copy is done bytewise - x2
will contain a byte-identical copy of x1
.
Not every type can be made Copy
- only those which have Copy
interior and do not implement Drop
. All primitive types (except &mut
references but including *const
and *mut
raw pointers) are Copy
in Rust, so each struct which contains only primitives can be made Copy
. On the other hand, structs like Vec
or Box
are not Copy
- they deliberately do not implement it because bytewise copy of them will lead to double frees because their destructors can be run twice over the same pointer.
The Copy
bit above is a slight digression on my side, just to give a clearer picture. Ownership in Rust is based on move semantics. When we say that some value own something, like in "Box<T>
owns the given T
", we mean semantic connection between them, not something magical or something which is built into the language. It is just most such values like Vec
or Box
do not implement Copy
and thus moved instead of copied, and they also (optionally) have a destructor which cleans up anything these types may have allocated for them (memory, sockets, files, etc.).
Given the above, of course you can write your own "owning" types. This is one of the cornerstones of idiomatic Rust, and a lot of code in the standard library and external libraries is written in such way. For example, some C APIs provide functions for creating and destroying objects. Writing an "owning" wrapper around them is very easy in Rust and it is probably very close to what you're asking for:
extern {
fn create_widget() -> *mut WidgetStruct;
fn destroy_widget(w: *mut WidgetStruct);
fn use_widget(w: *mut WidgetStruct) -> u32;
}
struct Widget(*mut WidgetStruct);
impl Drop for Widget {
fn drop(&mut self) {
unsafe { destroy_widget(self.0); }
}
}
impl Widget {
fn new() -> Widget { Widget(unsafe { create_widget() }) }
fn use_it(&mut self) -> u32 {
unsafe { use_widget(self.0) }
}
}
Now you can say that Widget
owns some foreign resource represented by *mut WidgetStruct
.
What is the right way to expose resources owned by a class?
As others have answered from a technical standpoint, I'd like to point you at a different approach and revise your design. The idea is to try to respect the Law of Demeter and don't grant access to object's sub-components. It's a bit harder to do, and without a specific example I can't provide many details but try to imagine a class Book formed of Pages. If I want to print one, two or more pages of the book, with your current design I can do:
auto range = ...;
for( auto p : book.pages(range) )
{
p->print();
}
while abiding by Demeter's you'll have
auto range = ...;
book.print( /* possibly a range here */ );
this is slanted towards better encapsulation as you don't rely upon internal details of the book class and if its internal structure changes, you don't need to do anything to your client code.
Smart pointers with a resource manager that lets objects borrow resources
If "it simply wants to use it", then SpriteTexture*
is just fine. Normal pointers are fine, as long as they don't have ownership semantics associated with them.
Just use your first solution (std::unique_ptr
) and dispense normal pointers obtained with get()
.
You just have to make sure there are no other parts of the code using such a pointer when the manager decides to get rid of the associated object, but that's the opposite problem to the one in your question. (You sort of implied this wouldn't be a problem in your application.)
A side note: I can't think of a reason to use a std::list
to hold those smart pointers. I think std::vector
should be your first choice, unless you have a specific reason not to use it here.
C++ shared_ptr vs. unique_ptr for resource management
Smart pointers like shared_ptr
and unique_ptr
are a good tools when you have owning pointers.
But for non-owning pointers, i.e. observing pointers, using a raw pointer is just fine.
In your design, I think the resource manager is the only "owner" of the resources, so you could simply have some form of smart pointer inside the resource manager. For example, the resource manager can have a std::vector<std::unique_ptr<Resource>>
as a data member, or even a simpler std::vector<Resource>
if your Resource
class is designed to be correctly storable in a std::vector
.
Then, the resource manager can give to the outside just non-owning observing pointers, and raw pointers (or C++ references) are fine for this case.
Of course, it's important that the lifetime of the resource manager exceeds that of the "resource clients".
Related Topics
Difference Between Const Int*, Const Int * Const, and Int Const *
How to Set, Clear, and Toggle a Single Bit
When Should Static_Cast, Dynamic_Cast, Const_Cast, and Reinterpret_Cast Be Used
When and Why Will a Compiler Initialise Memory to 0Xcd, 0Xdd, etc. on Malloc/Free/New/Delete
What Uses Are There For "Placement New"
Passing a 2D Array to a C++ Function
Rule-Of-Three Becomes Rule-Of-Five With C++11
What Are Aggregates and Pods and How/Why Are They Special
Start Thread With Member Function
What Is the "--≫" Operator in C++
What Is a Smart Pointer and When Should I Use One
Where Do I Find the Current C or C++ Standard Documents
Recursive Function That Returns All Substrings of a String
How to Calculate and Display the Average of the 5 Numbers in C++
Initialization of All Elements of an Array to One Default Value in C++