I want to write a ContentManager
class which loads and maintains different types of assets for a game (compare with XNA's ContentManager).
My header file looks like the following:
class ContentManager
{
public:
ContentManager(Direct3D& d3d, const std::string& rootDirectory = "Resource");
~ContentManager();
template<typename T>
const T& Load(const std::string& assetName);
private:
Direct3D& d3d_;
std::string rootDirectory_;
std::map<std::string, Texture> textures_;
};
As you can see, I have a map for each asset type (only for textures at the moment) and a generic Load<T>()
method, which I instantiate explicitly for each asset type I want to store. Load<Texture>()
reads an image file from disk (if it's not already in the map), creates a new Texture
, inserts it into the map and returns it.
My Texture
class basically creates and encapsulates a raw IDirect3DTexture9
to follow the RAII
idiom (the destructor calls Release()
on the texture_
):
class Texture
{
public:
Texture();
Texture(Direct3D& d3d, unsigned int width, unsigned int height,
const std::vector<unsigned char>& stream);
~Texture();
IDirect3DTexture9* GetD3DTexture() const { return texture_; }
private:
IDirect3DTexture9* texture_;
};
When testing my code, I realized that each texture was released twice, because (shallow) copies of the Texture
objects were created at some point, and the destructor was called for each of these, of course.
Furthermore, although Load<T>()
returns a reference to an element of the map, the caller could make a copy himself and trigger the same problem. My thoughts:
Making the copy constructor of
Texture
private is no solution, since copies are required anyway when creating anstd::pair
to insert a new element into the map.Defining a custom copy constructor, which creates a deep copy, doesn't seem to be reasonable at all, since I would need to create a copy of the underlying Direct3D texture, which should really exist only once.
So what are my options here? Could this be solved by using smart pointers? Which type should be stored in the map and which type should Load<T>()
return?
With C++11, the language added a feature called moving for exactly the reason you are encountering. And luckily, modifying your code to use move mechanics is amazingly simple:
This adds a "move constructor" and a "move assignment", which will move the content from one
Texture
to another, so that only one points to a givenIDirect3DTexture9
at a time. The compiler should detect these two, and stop generating the implicit copy constructor and copy assignment, so yourTexture
can no longer be copied, which is exactly what you wanted, since deep-copying aIDirect3DTexture9
is hard and doesn't even really make sense. The Texture class is now magically fixed.Now, what about the other classes? No changes.
std::map<std::string, Texture>
is smart enough to detect that your class hasnoexcept
move operators, and so it will use them automatically instead of copies. It also makes themap
itself movable but not copiable. And since themap
is movable but not copiable, that automagically makesContentManager
movable but not copiable. Which makes sense when you think about it, moving content around is fine, but you don't want to copy all of that. So those don't need any changesNow, since rvalues is obviously a new concept to you, here's a crash course: