Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Smart Pointer Semantics

I've been working with pointers for a few years now, but I only very recently decided to transition over to C++11's smart pointers (namely unique, shared, and weak). I've done a fair bit of research on them and these are the conclusions that I've drawn:

  1. Unique pointers are great. They manage their own memory and are as lightweight as raw pointers. Prefer unique_ptr over raw pointers as much as possible.
  2. Shared pointers are complicated. They have significant overhead due to reference counting. Pass them by const reference or regret the error of your ways. They're not evil, but should be used sparingly.
  3. Shared pointers should own objects; use weak pointers when ownership is not required. Locking a weak_ptr has equivalent overhead to the shared_ptr copy constructor.
  4. Continue to ignore the existence of auto_ptr, which is now deprecated anyhow.

So with these tenets in mind, I set off to revise my code base to utilize our new shiny smart pointers, fully intending to clear to board of as many raw pointers as possible. I've become confused, however, as to how best take advantage of the C++11 smart pointers.

Let's assume, for instance, that we were designing a simple game. We decide that it is optimal to load a fictional Texture data type into a TextureManager class. These textures are complex and so it is not feasible to pass them around by value. Moreover, let us assume that game objects need specific textures depending on their object type (i.e. car, boat, etc).

Prior, I would have loaded the textures into a vector (or other container like unordered_map) and stored pointers to these textures within each respective game object, such that they could refer to them when they needed to be rendered. Let's assume the textures are guaranteed to outlive their pointers.

My question, then, is how to best utilize smart pointers in this situation. I see few options:

  1. Store the textures directly in a container, then construct a unique_ptr in each game object.

    class TextureManager {   public:     const Texture& texture(const std::string& key) const         { return textures_.at(key); }   private:     std::unordered_map<std::string, Texture> textures_; }; class GameObject {   public:     void set_texture(const Texture& texture)         { texture_ = std::unique_ptr<Texture>(new Texture(texture)); }   private:     std::unique_ptr<Texture> texture_; }; 

    My understanding of this, however, is that a new texture would be copy-constructed from the passed reference, which would then be owned by the unique_ptr. This strikes me as highly undesirable, since I would have as many copies of the texture as game objects that use it -- defeating the point of pointers (no pun intended).

  2. Store not the textures directly, but their shared pointers in a container. Use make_shared to initialize the shared pointers. Construct weak pointers in the game objects.

    class TextureManager {   public:     const std::shared_ptr<Texture>& texture(const std::string& key) const         { return textures_.at(key); }   private:     std::unordered_map<std::string, std::shared_ptr<Texture>> textures_; }; class GameObject {   public:     void set_texture(const std::shared_ptr<Texture>& texture)         { texture_ = texture; }   private:     std::weak_ptr<Texture> texture_; }; 

    Unlike the unique_ptr case, I won't have to copy-construct the textures themselves, but rendering the game objects is expensive since I would have to lock the weak_ptr each time (as complex as copy-constructing a new shared_ptr).

So to summarize, my understanding is such: if I were to use unique pointers, I would have to copy-construct the textures; alternatively, if I were to use shared and weak pointers, I would have to essentially copy-construct the shared pointers each time a game object is to be drawn.

I understand that smart pointers are inherently going to be more complex than raw pointers and so I'm bound to have to take a loss somewhere, but both of these costs seem higher than perhaps they should be.

Could anybody point me in the correct direction?

Sorry for the long read, and thanks for your time!

like image 521
SamuelMS Avatar asked Nov 12 '13 13:11

SamuelMS


People also ask

Does C++11 have smart pointers?

Introduction of Smart PointersC++11 comes up with its own mechanism that's Smart Pointer. When the object is destroyed it frees the memory as well. So, we don't need to delete it as Smart Pointer does will handle it. A Smart Pointer is a wrapper class over a pointer with an operator like * and -> overloaded.

What is a smart pointer in C?

Smart pointer that enforces unique ownership by transferring ownership on copy. Comparable to the deprecated std::auto_ptr Class. CHeapPtr Class. Smart pointer for objects that are allocated by using the C malloc function.

Why auto_ptr is deprecated?

Why is auto_ptr deprecated? It takes ownership of the pointer in a way that no two pointers should contain the same object. Assignment transfers ownership and resets the rvalue auto pointer to a null pointer. Thus, they can't be used within STL containers due to the aforementioned inability to be copied.

How does a smart pointer work?

A smart pointer is like a regular (typed) pointer, like "char*", except when the pointer itself goes out of scope then what it points to is deleted as well. You can use it like you would a regular pointer, by using "->", but not if you need an actual pointer to the data. For that, you can use "&*ptr".


1 Answers

Even in C++11, raw pointers are still perfectly valid as non-owning references to objects. In your case, you're saying "Let's assume the textures are guaranteed to outlive their pointers." Which means you're perfectly safe to use raw pointers to the textures in the game objects. Inside the texture manager, store the textures either automatically (in a container which guarantees constant location in memory), or in a container of unique_ptrs.

If the outlive-the-pointer guarantee was not valid, it would make sense to store the textures in shared_ptr in the manager and use either shared_ptrs or weak_ptrs in the game objects, depending on the ownership semantics of the game objects with regards to the textures. You could even reverse that - store shared_ptrs in the objects and weak_ptrs in the manager. That way, the manager would serve as a cache - if a texture is requested and its weak_ptr is still valid, it will give out a copy of it. Otherwise, it will load the texture, give out a shared_ptr and keep a weak_ptr.

like image 162
Angew is no longer proud of SO Avatar answered Oct 02 '22 01:10

Angew is no longer proud of SO