我使用指针已经有几年了,但直到最近我才决定转向C++11的智能指针(即独特的、共享的和弱的)。我对它们做了相当多的研究,这些是我得出的结论:
因此,考虑到这些原则,我着手修改我的代码库,以利用我们的新的闪亮的智能指针,完全打算清除尽可能多的原始指针。然而,我对如何更好地利用C++11智能指针感到困惑。
让我们假设,例如,我们在设计一个简单的游戏。我们决定将虚构的纹理数据类型加载到TextureManager类中是最优的。这些纹理是复杂的,因此它是不可行的传递他们的价值。此外,让我们假设游戏对象需要特定的纹理,这取决于它们的对象类型(如汽车、船等)。
在此之前,我会将纹理加载到向量(或其他容器,如unordered_map)中,并在每个游戏对象中存储指向这些纹理的指针,这样它们就可以在需要呈现时引用它们。让我们假设纹理一定比它们的指针更持久。
那么,我的问题是,在这种情况下,如何最好地利用智能指针。我看不出有什么选择:
总之,我的理解是这样的:如果我要使用唯一的指针,我就必须复制-构造纹理;或者,如果我要使用共享的和弱的指针,我就必须在每次绘制游戏对象时从本质上复制构建共享的指针。
我知道智能指针本身就会比原始指针更复杂,所以我肯定会在某个地方蒙受损失,但这两种代价似乎都比它们应该付出的要高。
有人能指点我正确的方向吗?
很抱歉阅读了很长时间,谢谢你的时间!
发布于 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。
发布于 2013-11-13 23:41:18
总结一下您的用例:*)对象一旦创建,就不会被修改(我认为这是您的代码所暗示的) *)对象是引用的--可以按名称进行引用,并保证对应用程序所要求的任何名称都存在(我在推断--如果这不是真的,我将在下面讨论该做什么。)
这是一个令人愉快的用例。您可以在整个应用程序中对纹理使用值语义!这具有很好的性能和易于推理的优点。
一种方法是让您的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。但优点是,您有一个具有值语义和值语法的纹理类。
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作为包装类的不变量。这意味着如果纹理不存在,则从构造函数中抛出。这意味着您在尝试创建纹理时处理这个问题,而不是每次调用包装类的成员时都要检查空指针。
在回答您最初的问题时,智能指针对于生命周期管理是很有价值的,如果您只需要传递对其生存期超过指针的对象的引用,则并不特别有用。
发布于 2013-11-12 13:16:00
您可以拥有存储纹理的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_;
};然后,您可以在游戏对象类中使用纹理,而不是纹理*、weak_ptr等。
这样,纹理管理器就可以像缓存一样工作,可以重写get方法来搜索纹理,如果发现从映射返回纹理,则先加载它,然后将它移动到映射中,然后返回给它。
https://stackoverflow.com/questions/19929970
复制相似问题