What Is Ownership of Resources or Pointers

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



Leave a reply



Submit