当一个对象需要引用另一个对象而不“拥有它”(即,不对其生命周期负责)时,一种方法是简单地为此使用原始指针或原始引用,如下例所示:
class Node
{
std::vector<Edge*> incidentEdges;
};
class Edge
{
Node* startNode;
Node* endNode;
};
class Graph
{
std::vector<std::unique_ptr<Node*>> nodes;
std::vector<std::unique_ptr<Edge*>> edges;
};
(请节省时间来评论是否存在更有效的图形数据结构,这是我的专业领域,而不是问题的重点。)
Graph
负责节点和边的生命周期,并负责保证其中的指针Node
不Edge
悬空。但是如果程序员没有这样做,那么就有未定义行为的风险。
但是由于引用计数的开销成本,人们可以强烈地强制使用智能指针不会发生未定义的行为。相反,它会优雅地崩溃。它保证这发生在尽可能早的时间(避免损坏更多数据)并且不会被忽视。这是一种可能的实现:
(编辑:固定实现,Yakk回答中的更多细节。非常感谢!)
template <class T>
using owning_ptr = std::shared_ptr<T>;
template <class T>
class nonowning_ptr
{
std::weak_ptr p_;
public:
nonowning_ptr() : p_() {}
nonowning_ptr(const nonowning_ptr & p) : p_(p.p_) {}
nonowning_ptr(const owning_ptr<T> & p) : p_(p) {}
// checked dereferencing
owning_ptr<T> get() const
{
if (auto sp = p_.lock())
{
return sp.get();
}
else
{
logUsefulInfo();
saveRecoverableUserData();
nicelyInformUserAboutError();
abort(); // or throw exception
}
}
T & operator*() const = delete; // cannot be made safe
owning_ptr<T> operator->() const { return get(); }
// [...] other methods forwarding weak_ptr functionality
};
class Node
{
std::vector<nonowning_ptr<Edge>> incidentEdges;
};
class Edge
{
nonowning_ptr<Node> startNode;
nonowning_ptr<Node> endNode;
};
class Graph
{
std::vector<owning_ptr<Node>>> nodes;
std::vector<owning_ptr<Edge>>> edges;
};
我的问题是:除了明显的性能与安全性权衡之外,每种方法的优缺点是什么?
我不是在问哪个是最好的,肯定没有最好的,这取决于用例。我在询问您可能知道而我不知道的每种方法的事实利弊,这将有助于做出设计决策(也许,在可读性方面?可维护性?可移植性?与第三方库玩得很好?防止 use-after-free 漏洞利用?)。