首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++11智能指针语义

C++11智能指针语义
EN

Stack Overflow用户
提问于 2013-11-12 13:03:29
回答 4查看 8.1K关注 0票数 37

我使用指针已经有几年了,但直到最近我才决定转向C++11的智能指针(即独特的、共享的和弱的)。我对它们做了相当多的研究,这些是我得出的结论:

  1. 独特的指针是很棒的。它们管理自己的内存,并且与原始指针一样轻量级。与原始指针相比,更喜欢unique_ptr。
  2. 共享指针很复杂。由于参考计数,它们有很大的开销。把它们传递给对方,否则你会后悔自己的错误。它们不是邪恶的,但应该谨慎使用。
  3. 共享指针应该拥有对象;在不需要所有权时使用弱指针。锁定weak_ptr的开销相当于shared_ptr复制构造函数的开销。
  4. 继续忽略auto_ptr的存在,不管怎么说,它现在已经不再受欢迎了。

因此,考虑到这些原则,我着手修改我的代码库,以利用我们的新的闪亮的智能指针,完全打算清除尽可能多的原始指针。然而,我对如何更好地利用C++11智能指针感到困惑。

让我们假设,例如,我们在设计一个简单的游戏。我们决定将虚构的纹理数据类型加载到TextureManager类中是最优的。这些纹理是复杂的,因此它是不可行的传递他们的价值。此外,让我们假设游戏对象需要特定的纹理,这取决于它们的对象类型(如汽车、船等)。

在此之前,我会将纹理加载到向量(或其他容器,如unordered_map)中,并在每个游戏对象中存储指向这些纹理的指针,这样它们就可以在需要呈现时引用它们。让我们假设纹理一定比它们的指针更持久。

那么,我的问题是,在这种情况下,如何最好地利用智能指针。我看不出有什么选择:

  1. 将纹理直接存储在容器中,然后在每个游戏对象中构造一个unique_ptr。 类TextureManager { public: const纹理和纹理( const std::string& key) const{返回textures_.at(key);}私有: std::unordered_map textures_;};类GameObject { public: void set_texture(const纹理和纹理){ texture_ = std::unique_ptr (新纹理(纹理));}私有:std::unique_ptr texture_;}; 然而,我对此的理解是,将从传递的引用复制一个新的纹理,然后该引用将由unique_ptr拥有。这让我觉得非常不受欢迎,因为我的纹理拷贝和使用它的游戏对象一样多
  2. 不是直接存储纹理,而是将它们的共享指针存储在容器中。使用make_shared初始化共享指针。在游戏对象中构造弱指针。 类TextureManager { public: const std::shared_ptr&纹理( const std::string& key) const{返回textures_.at(key);}私有: std::unordered_map textures_;};类GameObject { public: void set_texture(const std::shared_ptr&纹理){ texture_ =纹理;}私有: std::weak_ptr texture_;}; 与unique_ptr的情况不同,我不需要复制-构造纹理本身,但是渲染游戏对象是昂贵的,因为每次我都要锁定weak_ptr (就像复制构建一个新的shared_ptr一样复杂)。

总之,我的理解是这样的:如果我要使用唯一的指针,我就必须复制-构造纹理;或者,如果我要使用共享的和弱的指针,我就必须在每次绘制游戏对象时从本质上复制构建共享的指针。

我知道智能指针本身就会比原始指针更复杂,所以我肯定会在某个地方蒙受损失,但这两种代价似乎都比它们应该付出的要高。

有人能指点我正确的方向吗?

很抱歉阅读了很长时间,谢谢你的时间!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-11-12 13:11:46

即使在C++11中,作为对象的非拥有引用,原始指针仍然是完全有效的。在你的例子中,你说的是“让我们假设纹理被保证超过它们的指针。”这意味着你非常安全地使用原始指针到游戏对象的纹理。在纹理管理器中,自动存储纹理(在内存中保证恒定位置的容器中),或者存储在unique_ptr的容器中。

如果比指针更有效的保证是不有效,那么在管理器中存储shared_ptr中的纹理并在游戏对象中使用shared_ptrs或weak_ptrs是有意义的,这取决于游戏对象相对于纹理的所有权语义。您甚至可以在对象中反向存储shared_ptr,在管理器中反向存储weak_ptr。这样,管理器将充当缓存--如果请求纹理,并且它的weak_ptr仍然有效,它将给出它的副本。否则,它将加载纹理,发出一个shared_ptr,并保留一个weak_ptr

票数 31
EN

Stack Overflow用户

发布于 2013-11-13 23:41:18

总结一下您的用例:*)对象一旦创建,就不会被修改(我认为这是您的代码所暗示的) *)对象是引用的--可以按名称进行引用,并保证对应用程序所要求的任何名称都存在(我在推断--如果这不是真的,我将在下面讨论该做什么。)

这是一个令人愉快的用例。您可以在整个应用程序中对纹理使用值语义!这具有很好的性能和易于推理的优点。

一种方法是让您的TextureManager返回一个纹理const*。考虑:

代码语言:javascript
复制
using TextureRef = Texture const*;
...
TextureRef TextureManager::texture(const std::string& key) const;

由于下面的纹理对象具有应用程序的生存期,从未修改过,并且始终存在(您的指针永远不是nullptr),所以您只需将TextureRef视为简单的值。您可以传递它们,返回它们,比较它们,并为它们制作容器。它们很容易推理,而且工作效率很高。

这里的麻烦在于,您有值语义(这很好),但是指针语法(对于类型和值语义来说可能会混淆)。换句话说,要访问纹理类的成员,需要执行如下操作:

代码语言:javascript
复制
TextureRef t{texture_manager.texture("grass")};

// You can treat t as a value. You can pass it, return it, compare it,
// or put it in a container.
// But you use it like a pointer.

double aspect_ratio{t->get_aspect_ratio()};

处理这一问题的一种方法是使用类似pimpl的成语,并创建一个类,它只不过是指向纹理实现的指针的包装器。这需要更多的工作,因为最终您将为纹理包装类创建一个API (成员函数),该类转发到您的实现类的API。但优点是,您有一个具有值语义和值语法的纹理类。

代码语言:javascript
复制
struct Texture
{
  Texture(std::string const& texture_name):
    pimpl_{texture_manager.texture(texture_name)}
  {
    // Either
    assert(pimpl_);
    // or
    if (not pimpl_) {throw /*an appropriate exception */;}
    // or do nothing if TextureManager::texture() throws when name not found.
  }
  ...
  double get_aspect_ratio() const {return pimpl_->get_aspect_ratio();}
  ...
  private:
  TextureImpl const* pimpl_; // invariant: != nullptr
};

..。

代码语言:javascript
复制
Texture t{"grass"};

// t has both value semantics and value syntax.
// Treat it just like int (if int had member functions)
// or like std::string (except lighter weight for copying).

double aspect_ratio{t.get_aspect_ratio()};

我已经假设,在你的游戏中,你永远不会要求一个纹理是不一定存在的。如果是这样的话,那么您可以断言名称存在。但是如果不是这样的话,那么你需要决定如何处理这种情况。我的建议是将指针不能是nullptr作为包装类的不变量。这意味着如果纹理不存在,则从构造函数中抛出。这意味着您在尝试创建纹理时处理这个问题,而不是每次调用包装类的成员时都要检查空指针。

在回答您最初的问题时,智能指针对于生命周期管理是很有价值的,如果您只需要传递对其生存期超过指针的对象的引用,则并不特别有用。

票数 11
EN

Stack Overflow用户

发布于 2013-11-12 13:16:00

您可以拥有存储纹理的std::unique_ptrs的std::map。然后,您可以编写一个get方法,该方法按名称返回对纹理的引用。这样,如果每个模型都知道其纹理的名称(应该是这样的),您可以简单地将名称传递到get方法中,并从映射中检索引用。

代码语言:javascript
复制
class TextureManager 
{
  public:
    Texture& get_texture(const std::string& key) const
        { return *textures_.at(key); }
  private:
    std::unordered_map<std::string, std::unique_ptr<Texture>> textures_;
};

然后,您可以在游戏对象类中使用纹理,而不是纹理*、weak_ptr等。

这样,纹理管理器就可以像缓存一样工作,可以重写get方法来搜索纹理,如果发现从映射返回纹理,则先加载它,然后将它移动到映射中,然后返回给它。

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

https://stackoverflow.com/questions/19929970

复制
相关文章

相似问题

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