A common design feature throughout Cinder is the idea of implicitly shared objects. To look at how this works, let's consider the following bit of code:
In this instance, textureB becomes another handle to the same texture as textureA. Consider for a moment the alternative. If the assignment above were to create a clone of the texture, this would be an extremely expensive operation. We'd need to copy all of the pixels off of the GPU, allocate a new Texture, and then upload a copy of all of these pixels back to the GPU. That's rough. However, the way gl::Texture and other classes like it are designed, this assignment is instead very fast. Basically assignment says, "here's the OpenGL Texture I'm referring to. You refer to it too."
This is especially convenient in a couple of scenarios. First, returning Cinder objects from functions:
In the hypothetical example above, we have a function which returns a new OpenGL texture as its result. This is made efficient and easy because of implict sharing. myCheckerboard is made to refer to the same gl::Texture which was allocated and returned by the createCheckerboardTexture() function. The implictly shared object design allows you to write natural code like this without worrying about the performance or safety of returning a class as a result.
A second common use case is creating STL containers like std::vector<>
out of Cinder's core types:
This code allocates an STL vector of textures, and then appends to this vector the result of a call to our createCheckerboardTexture() function. Safe, fast & easy.
One of the most important advantages of the implicitly shared design is that it makes it nearly impossible to leak memory or other resources. This is because implicitly shared objects are able to sort out amongst themselves when a resource is no longer being used, and will free that resource as soon as nobody is interested in it anymore. Last one to leave turns out the lights. This is very similar to garbage collection, but is even more powerful for the fact that resources are freed immediately, and not at some undetermined future time. Consider this code:
At line 3, temp1 is allocated and assigned a new texture. At line 4, temp2 is also allocated and assigned a different texture. At line 5, we assign outside to refer to the same texture as temp1. At line 6, both temp1 and temp2 go out of scope, and their destructors are called. No other gl::Texture refers to the texture allocated at line 4, so this OpenGL texture is destroyed automatically and immediately. However outside still exists, and still refers to the same texture temp1 did, so when temp1 is destructed, the texture it referred to is not destroyed. You can trust that Cinder's classes will clean up after themselves, making it very difficult to write the sorts of leaks or double-frees that keep C++ programmers awake at night.
One useful feature of this design is that implicitly shared objects can be the equivalent of a null pointer, which is to say they can be empty. The default constructor will allocate an object in this null state:
We can test this in the same ways we might test a pointer or a boolean
You don't need to understand how Cinder classes implement this implicit sharing to make use of it. If you're just getting started with C++, you can stop reading, trusting that Cinder is managing things behind the scenes for you. But for more advanced users it's a helpful thing to understand, especially if you intend to contribute code back to the Cinder community. Let's continue using the gl::Texture class as an example. If you open gl/Texture.h and look inside, you'll see that it contains a member class called Obj. This class actually contains the "guts" you might have expected to see inside gl::Texture itself - things like the width of the texture in pixels (mWidth), or the OpenGL texture ID (mTextureID). The only member variable gl::Texture itself contains is a std::shared_ptr to one of these Obj's, called mObj. Now take a look at how a function like gl::Texture::getId() is implemented:
As you can see, the gl::Texture returns its Obj's mTextureID. You'll find this sort of pattern used throughout gl::Texture's implementation. Now consider what an assignment does:
While we did not have to write it explicitly (because C++ implicitly generated our operator= and copy constructor for us), this is the equivalent of:
Because of the way that shared_ptr's work, both textureA and textureB point to the same Obj. The fact that this is implemented using a shared_ptr is an implementation detail - the same effect could be achieved with a different technique, but shared_ptr's are a proven, easy way to make this possible, and it's one we'd recommend if you're designing your own classes to behave similarly.
This implicitly shared object pattern is not something unique to Cinder. If you are interested to see examples of it in other C++ class libraries, you can read about Qt's implementation here, wxWidgets' here or take a look at the design of OpenCV's memory management.