C++:存储复制操作不合理或不可能的资源

标签 c++ directx directx-9 move-constructor

我想写一个ContentManager为游戏加载和维护不同类型 Assets 的类(与 XNA's ContentManager 比较)。 我的头文件如下所示:

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_;
};

如您所见,我为每种 Assets 类型都有一张 map (目前仅针对纹理)和一个通用的 Load<T>()方法,我为要存储的每种 Assets 类型显式实例化了该方法。 Load<Texture>()从磁盘读取图像文件(如果它不在 map 中),创建一个新的 Texture , 将其插入 map 并返回。

我的 Texture类基本上创建并封装了一个原始的 IDirect3DTexture9关注RAII习语(析构函数在 Release() 上调用 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_;
};

在测试我的代码时,我意识到每个纹理都被释放了两次,因为 Texture 的(浅)拷贝对象在某个时候被创建,当然,每个对象都调用了析构函数。

此外,尽管Load<T>()返回对 map 元素的引用,调用者可以自己制作一个拷贝并触发相同的问题。我的想法:

  • 制作Texture的拷贝构造函数private 不是解决方案,因为在创建 std::pair 时无论如何都需要拷贝将新元素插入 map 。

  • 定义一个创建深层拷贝的自定义复制构造函数似乎根本不合理,因为我需要创建一个底层 Direct3D 纹理的拷贝,它实际上应该只存在一次。

那么我的选择是什么?这可以通过使用智能指针来解决吗?哪种类型应该存储在 map 中,哪种类型应该 Load<T>()返回?

最佳答案

在 C++11 中,该语言添加了一项称为移动的功能,正是您遇到的原因。幸运的是,修改代码以使用移动机制非常简单:

class Texture
{
public:
  Texture() noexcept;
  Texture(Direct3D& d3d, unsigned int width, unsigned int height, 
    const std::vector<unsigned char>& stream);

  Texture(Texture&& rhs) noexcept
  : texture_(rhs.texture_) //take the content from rhs
  {rhs.texture_ = nullptr;} //rhs no longer owns the content

  Texture& operator=(Texture&& rhs) noexcept
  {
    Clear(); //erase previous content if any
    texture_ = rhs.texture_; //take the content from rhs
    rhs.texture_ = nullptr; //rhs no longer owns the content
    return *this;
  }

  ~Texture() noexcept {Clear();}

  IDirect3DTexture9* GetD3DTexture() const noexcept { return texture_; }

private:
  void Clear() noexcept; //does nothing if texture_ is nullptr
  IDirect3DTexture9* texture_;
};

这会添加一个“移动构造函数”和一个“移动赋值”,这将移动来自 Texture 的内容。到另一个,这样只有一个指向给定的 IDirect3DTexture9一次。编译器应该检测到这两个,并停止生成隐式复制构造函数和复制赋值,所以你的 Texture不能再被复制,这正是你想要的,因为深度复制 IDirect3DTexture9很难,甚至没有真正意义。 Texture 类现在神奇地修复了。

现在,其他类呢?没有变化。 std::map<std::string, Texture>足够聪明,可以检测到你的类(class)有 noexcept移动运算符,因此它将自动使用它们而不是拷贝。它还使 map本身可移动但不可复制。自 map是可移动但不可复制的,它会自动生成 ContentManager可移动但不可复制。当您考虑时,这是有道理的,四处移动内容很好,但您不想复制所有这些内容。所以那些不需要任何改变


现在,由于右值对您来说显然是一个新概念,这里有一个速成类(class):

Texture getter(); //returns a temporary Texture
Texture a = getter(); //since the right hand side is a temporary,
                        //the compiler will try to move construct it, and only 
                        //copy construct if it can't be moved.
a = getter(); //since the right hand side is a temporary,
                        //the compiler will try to move assign it, and only 
                        //copy assign if it can't be moved.

void setter1(Texture&); //receives a reference to an outside texture object
setter1(a); //works exactly as it always has
setter1(getter()); //compiler error, reference to temporary
setter1(std::move(a)); //compiler error, passing rreference && instead of lreference &.

void setter2(Texture); //receives a local texture object
setter2(a); //compiler error, no copy constructor
setter1(getter()); //creates the Texture by moving the data from the temporary
setter2(std::move(a)); //The compiler moves the content from a to the function;
                      //the function receives the content previously held by a, and 
                      //a now has no content.  Careful not to read from a after this.

void setter3(Texture&&); //receives a reference to a temporary texture
setter3(a); //compiler error, a is not a temporary
setter3(getter()); //function receives reference to the temporary, all is good
setter3(std::move(a)); //function thinks a is temporary, and will probably remove 
                       //it's content. Careful not to read from a after this.   

关于C++:存储复制操作不合理或不可能的资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18449770/

相关文章:

c++ - 不同的实例化方式

c++ - 在C++中实现多个接口(interface)

c# - 如何使用 XAudio2 重复播放相同的声音?

c++ - Direct3D 9.0 交错非 fvf 顶点缓冲区

c++ - 如何在 DirectX 9 中将 XMMATRIX 转换为 D3DMATRIX?

c++ - 当包含头文件的文件不在根项目文件夹中时如何包含头文件?

c++ - 有没有一种方法可以衡量应用程序/程序占用了多少系统资源(例如 RAM)

c# - Windows 7/8、Direct3D9、10 和 11 - 效果/着色器

c++ - DirectX:2 个 Sprite 多边形之间的小失真

c++ - directx 旋转 C++