shared_ptr<int> ptr = make_shared<int>(10);
int& val = *ptr;
ptr = make_shared<int>(20);
// val now points to freed memory
在上面的代码中,我可以读取和写入val,该val指向释放的内存。如果我们在shared_ptr中使用.get(),则会出现相同的问题。因此,即使我们诉诸使用智能指针,也可以朝自己的脚射击。
显然没有人会像上面那样编码。解决这个问题的一种方法是,如果我们有这样的东西-
class Foo {
public:
int& getVal() { return *p; }
private:
shared_ptr<int> p;
};
有人可以调用getVal(),之后上述类的其他一些成员可以选择用其他值覆盖p。如果上面的getVal()返回shared_ptr而不是引用,则不会出现此问题。有些人可能会争辩说,因为我们需要在shared_ptr控制块中增加计数器,所以返回shared_ptr比返回引用更昂贵。
那么,指南应该不要像上面那样返回对shared_ptr的引用吗?
答案 0 :(得分:4)
这里不是危险,operator*
是在存储引用。
存储对任何内容的引用意味着您要对所引用内容的生命周期负责。
int getVal() { return *p; }
是最安全,最简单的解决方案。 C ++重视价值。
有一个更复杂的对象,复制起来很昂贵吗?然后请多加注意。
gsl::span<int const> getVals() { return {p->data(), p->size()}; }
在这里,尽管不是引用或指针类型,但在概念上还是引用。
它的消费者必须知道getVals
返回的生命周期规则。如果他们要保留它,则负责复制数据。
仅在极端情况下,您应考虑:
std::shared_ptr<gsl::span<int const>> getVals() {
return p?make_shared_span<int const>( p, {p->data(), p->size()} ):nullptr;
}
其中make_shared_span
将共享的跨距视图创建为共享的ptr。
共享对可变数据的引用特别令人恐惧。您永远不会知道数据是否会在您的脚下改变,这会使使用它的任何代码的状态都与也引用它的所有代码的状态纠缠在一起。
C ++代码库避开“共享引用”和生存期问题的一种方法是使用不可变数据。
与shared_ptr<gsl::span<int const>const>
相比,{em}实际上是不可改变的shared_ptr<gsl::span<int>>
更为理智。
您会在设计中看到这一点,例如here或使用immutable libgit store支持文档。
通过共享指向数据的所有指针来“解决”这一问题,将导致对象的生存期大大超过其应有的寿命,因为人们存储了指向它们的共享指针,而不再打算再次使用它。
关注生命,您将获得出色的RAII结果。
在某些情况下,共享共享指针是有意义的,也就是说,当您实际上想要共享所有权时,而不是在“我不想考虑终身”时。共享所有权是一种更复杂的生命周期,使用它是因为在某些情况下,您不必考虑生命周期对意大利面条的影响。