首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++:存储副本操作不合理或不可能的资源

C++:存储副本操作不合理或不可能的资源
EN

Stack Overflow用户
提问于 2013-08-26 17:37:59
回答 5查看 274关注 0票数 3

我想编写一个ContentManager类,它为游戏加载和维护不同类型的资产(与XNA的ContentManager相比)。我的头文件如下所示:

代码语言:javascript
复制
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_;
};

如您所见,我为每个资产类型提供了一个映射(目前仅针对纹理)和一个泛型的Load<T>()方法,我对要存储的每个资产类型都显式地实例化了该方法。Load<Texture>()从磁盘中读取图像文件(如果它尚未在映射中),创建一个新的Texture,将其插入到映射中并返回。

我的Texture类基本上创建并封装了一个原始IDirect3DTexture9,以遵循RAII成语(析构函数在texture_上调用Release() ):

代码语言:javascript
复制
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>()返回对映射元素的引用,但调用方可以自己复制并触发相同的问题。我的想法:

  • Texture的复制构造函数设置为私有并不是解决方案,因为在创建std::pair以将新元素插入到映射中时,无论如何都需要复制。
  • 定义一个自定义复制构造函数(它创建一个深度副本)似乎根本不合理,因为我需要创建底层Direct3D纹理的副本,它应该只存在一次。

我在这里有什么选择?能否通过使用智能指针来解决这个问题?应该将哪种类型存储在映射中,Load<T>()应该返回哪种类型?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-08-26 17:53:23

使用C++11,该语言添加了一个名为“移动”的特性,这正是您遇到的原因。幸运的是,修改代码以使用移动机制非常简单:

代码语言:javascript
复制
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,因此一次只指向一个给定的IDirect3DTexture9。编译器应该检测到这两者,并停止生成隐式复制构造函数和复制赋值,这样您的Texture就不能再被复制,这正是您想要的,因为深入复制IDirect3DTexture9是很困难的,甚至没有真正的意义。纹理类现在已经神奇地修复了。

那其他班级呢?没有变化。std::map<std::string, Texture>足够聪明地检测到您的类中有noexcept移动运算符,因此它将自动使用它们而不是副本。它还使map本身具有可移动性,但不可复制。由于map是可移动但不可复制的,这就自动地使ContentManager可移动但不可复制。这是有道理的,当你想到它,移动内容是好的,但你不想复制所有这些。所以这些不需要任何改变

现在,由于rvalue对您来说显然是一个新概念,下面是一个速成课程:

代码语言:javascript
复制
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.   
票数 5
EN

Stack Overflow用户

发布于 2013-08-26 17:41:00

将构造函数设置为私有。

创建一个包含指向对象的指针的映射,而不是对象本身。

票数 1
EN

Stack Overflow用户

发布于 2013-08-26 17:43:47

最好是为每个对象保留一个参考计数器。因此,我将使用一个共享指针(std::map< std::string,std::tr1::shared_ptr< .> >)。

您还可以尝试使用内容管理器来释放对象(即: Release( T &object )),并在映射本身中保留一个引用计数器。但最好的是共享指针。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18449770

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档