提问者:小点点

C++11智能指针语义


我使用指针已经有几年了,但是直到最近我才决定过渡到C++11的智能指针(即unique,shared和Weake)。我对它们做了大量的研究,得出的结论如下:

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

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

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

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

>

class TextureManager {
  public:
    const Texture& texture(const std::string& key) const
        { return textures_.at(key); }
  private:
    std::unordered_map<std::string, Texture> textures_;
};
class GameObject {
  public:
    void set_texture(const Texture& texture)
        { texture_ = std::unique_ptr<Texture>(new Texture(texture)); }
  private:
    std::unique_ptr<Texture> texture_;
};

然而,我对此的理解是,将从传递的引用复制构造一个新的纹理,然后该纹理将归unique_ptr所有。这对我来说是非常不可取的,因为我会有和使用它的游戏对象一样多的纹理副本--挫败指针的指向(没有双关语的意思)。

不要直接存储纹理,而是在容器中存储它们的共享指针。使用make_shared初始化共享指针。在游戏对象中构造弱指针。

class TextureManager {
  public:
    const std::shared_ptr<Texture>& texture(const std::string& key) const
        { return textures_.at(key); }
  private:
    std::unordered_map<std::string, std::shared_ptr<Texture>> textures_;
};
class GameObject {
  public:
    void set_texture(const std::shared_ptr<Texture>& texture)
        { texture_ = texture; }
  private:
    std::weak_ptr<Texture> texture_;
};

与unique_ptr不同,我不必复制构造纹理本身,但是渲染游戏对象是昂贵的,因为我必须每次锁定weak_ptr(就像复制构造一个新的shared_ptr一样复杂)。

因此,总结一下,我的理解是这样的:如果我要使用独特的指针,我将不得不复制构建文本;或者,如果我要使用共享指针和弱指针,那么每次绘制游戏对象时,我必须基本上复制构建共享指针。

我知道智能指针本来就会比原始指针复杂,所以我肯定会在某些地方有所损失,但这两个成本似乎都比应该的要高。

谁能告诉我正确的方向吗?

很抱歉阅读了这么长的时间,谢谢您的时间!


共3个答案

匿名用户

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

如果outlive-the-pointer保证无效,将纹理存储在管理器中的中,并在游戏对象中使用仍然有效,它就会给出一个纹理的副本。否则,它将加载纹理,给出一个并保留一个

匿名用户

总结一下您的用例:*)对象被保证比用户的寿命长*)对象一旦被创建,就不会被修改(我想这是您的代码所暗示的)*)对象可以通过名称被引用,并且被保证以您的应用程序所要求的任何名称存在(我在外推--如果这不是真的,我将在下面讨论该怎么做。)

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

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

using TextureRef = Texture const*;
...
TextureRef TextureManager::texture(const std::string& key) const;

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

这里令人烦恼的是,您有值语义(这是好的),但是指针语法(对于具有值语义的类型来说,这可能会令人困惑)。换句话说,要访问纹理类的成员,您需要执行如下操作:

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转发到您的实现类的API。但好处是您有一个同时具有值语义和值语法的纹理类。

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

。。。

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作为包装类的一个不变量。这意味着如果纹理不存在,则从构造函数抛出。这意味着您可以在创建纹理时处理这个问题,而不是每次调用包装器类的成员时都要检查空指针。

在回答您最初的问题时,智能指针对于生存期管理很有价值,如果您所需要的只是将引用传递到其生存期保证超过指针的对象,那么智能指针就没有特别的用处。

匿名用户

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

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

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

这样纹理管理器就可以像缓存一样工作,get方法可以被重新编写来搜索纹理,如果找到了就从地图返回它,否则首先加载它,将它移动到地图,然后返回一个对它的引用

相关问题


MySQL Query : SELECT * FROM v9_ask_question WHERE 1=1 AND question regexp '(c++11|智能|指针|语义)' ORDER BY qid DESC LIMIT 20
MySQL Error : Got error 'repetition-operator operand invalid' from regexp
MySQL Errno : 1139
Message : Got error 'repetition-operator operand invalid' from regexp
Need Help?