将shared_ptr的嵌套智能指针重置为shared_ptr(或unique_ptr),看似悖论

时间:2014-11-05 02:12:06

标签: c++ c++11 shared-ptr unique-ptr nested

我知道std::shared_ptr管理的对象不是delete d reset(),除非它是唯一在该点管理它的shared_ptr。我知道当有多个shared_ptr管理同一个对象时,对托管对象值的更改会反映在指向它的所有shared_ptr中,而对这些shared_ptr的任何更改都会发生变化由reset()引起它的值(其管理对象的值)(即将shared_ptr从指向原始管理对象的对象更改为指向无对象的对象或其他东西)不会更改其他shared_ptr s'值(即它们仍然指向原始托管对象,原始托管对象仍然存在):

#include <memory>
#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<shared_ptr<int>> vec{ make_shared<int>(5) };
    shared_ptr<int> sptr(vec[0]);
    ++ *sptr;
    cout << *vec[0] << endl; // 6
    vec[0].reset();
    vec.pop_back();
    cout << *sptr << endl;   // 6
}

但是当使用两个级别的间接时,我失去了这种逻辑。给定一个名为Wrappershared_ptr<shared_ptr<Wrapper>>的类以及先前初始化的任意数量的其他shared_ptr<shared_ptr<Wrapper>>,为什么此配置允许在任何内部{{1}上调用reset()有效shared_ptr所有其他内部reset()

我的猜测是:任何 out er shared_ptr的托管对象是内部shared_ptr(而不是shared_ptr)并且更改为内部Wrapper的值{shared_ptr内部reset(),它将内部shared_ptr的值从指向shared_ptr实例的值更改为在所有 out er Wrapper中都会反映出一个无所不知的东西,有效地导致所有 out er shared_ptr ■失去对shared_ptr实例的间接管理,从而删除Wrapper实例。

但是同样的逻辑,是不是重置一个内部指针只会导致特定的内部指针失去对Wrapper的管理?鉴于所有其他外部指针指向它们自己的内部指针(即用它们构造的那些),那些外部指针不会继续对Wrapper进行间接管理,因为重置一个内部指针不会不要改变Wrapper的值,其他内部指针仍然可以访问它?这对我来说是一个悖论。

如果重置一个内部指针有效地重置所有内部指针,那么这意味着内部指针&#39;在Wrapper之前use_count() 1reset()。我认为多个shared_ptr可以出现来管理同一个对象同时将use_count()保持在1的唯一方法是通过幻觉:他们管理不同的对象(即具有相同值的不同地址的对象。我通过制作一个名为int的{​​{1}}包装器进行了测试,其中唯一的数据成员是包裹的Wrapperint static,用于跟踪数量当前存在的instance_count个实例。

Wrapper

显然有struct Wrapper { Wrapper(int par = 0) : num(par) { ++instance_count; } Wrapper(const Wrapper& src) : num(src.num) { ++instance_count; } ~Wrapper() { --instance_count; } int num; static int instance_count; }; int Wrapper::instance_count = 0; int main() { shared_ptr<shared_ptr<Wrapper>> dual_ptr_1( make_shared<shared_ptr<Wrapper>>( make_shared<Wrapper>(Wrapper(5)) ) ); // - Output - cout << Wrapper::instance_count << endl; // 1 shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(dual_ptr_1); cout << Wrapper::instance_count << endl; // 1 cout << dual_ptr_1->use_count() << endl; // 1 cout << dual_ptr_2->use_count() << endl; // 1 cout << dual_ptr_1.use_count() << endl; // 2 cout << dual_ptr_2.use_count() << endl; // 2 // note that above, the '->' operator accesses // inner ptr while '.' operator is for outer ptr cout << (*dual_ptr_1)->num << endl; // 5 cout << (*dual_ptr_2)->num << endl; // 5 dual_ptr_2->reset(); cout << Wrapper::instance_count << endl; // 0 cout << dual_ptr_1->use_count() << endl; // 0 cout << dual_ptr_2->use_count() << endl; // 0 cout << dual_ptr_1.use_count() << endl; // 2 cout << dual_ptr_2.use_count() << endl; // 2 } 个内部指针指向2 1个对象;内心指针&#39; Wrapper最多use_count(销毁前); 1班级Wrapper最多为instance_count(销毁前);并且可以通过两个外部指针访问间接管理的1对象(这意味着外部指针都没有被另一个指针移动构造);并重置一个内部指针有效地重置所有这些;所以我仍然不理解看似悖论。

我也在这篇文章中提出了相同的问题,其中上述代码将内部Wrapper替换为shared_ptr s,内部unique_ptr被替换由make_sharedmake_unique注释掉内部指针(因为use_count()缺少该方法),它提供相同的输出。这对我来说似乎是一个悖论,因为unique_ptr在这里看起来并不独特。

2 个答案:

答案 0 :(得分:3)

  

给定一个名为Wrappershared_ptr<shared_ptr<Wrapper>>的类   任何数量的其他shared_ptr<shared_ptr<Wrapper>> s初始化为   之前,为什么这个配置允许reset()调用任何   内部shared_ptr有效reset()所有其他内部shared_ptrs

没有其他内部shared_ptr,你有一个包含对象的实例,即

dual_ptr_1
          \
           --> shared_ptr --> Wrapper
          /
dual_ptr_2

而不是

dual_ptr_1 --> shared_ptr 
                         \
                          --> Wrapper
                         /
dual_ptr_2 --> shared_ptr 

致电dual_ptr_2->reset();后,此更改为

dual_ptr_1
          \
           --> shared_ptr --> (empty)
          /
dual_ptr_2

答案 1 :(得分:1)

接受的答案用图表显示OP代码中发生的情况:两个外部shared_ptr指向同一内部shared_ptr,指向Wrapper对象。 (我参考接受答案的未经编辑的版本中的图表;在我的回答时尚未对其进行编辑。)接受的答案有另一个图表,其中显示了OP预期会发生但未发生的情况,我称之为:

案例A - 两个指向不同内部指针的外部指针,指向同一个Wrapper对象(请参阅图表的已接受答案)。

以下是导致案例A的代码:

int main() {
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
        make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
    );
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
        make_shared<shared_ptr<Wrapper>>(*dual_ptr_1)
    );
    cout << dual_ptr_1.use_count() << endl;  // 1
    cout << dual_ptr_2.use_count() << endl;  // 1
    cout << dual_ptr_1->use_count() << endl; // 2
    cout << dual_ptr_2->use_count() << endl; // 2
}

我将dual_ptr_1称为第一个外部指针,并将shared_ptr称为第一个内部指针。我将dual_ptr_2称为第二个外部指针,将shared_ptr称为第二个内部指针。两个外部指针指向不同的内部指针。第二个外部指针未复制构造或分配给第一个外部指针,因此外部指针的use_count1。第二个外部指针不指向第一个内部指针,而是指向从第一个内部指针复制构造的无名内部指针。虽然第二个外部指针仍在管理第二个内部指针,但后者的无名称不会导致后者超出范围。第二个内部指针指向与第一个内部指针相同的Wrapper,因为第二个内部指针是从第一个内部指针复制构造的。由于此shared_ptr复制构造,内部指针的use_count2。每个内部指针必须为reset()为零或其他东西,分配给其他东西,或超出范围以便Wrapper被销毁(内部指针不需要经过相同的操作,只要每个操作至少经历其中一个)。

这是另一个,案例B - 与案例A相同的图表,但是有一个错误的实现和不同的控制台输出:

int main() {
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
        make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
    );
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
        make_shared<shared_ptr<Wrapper>>(&(**dual_ptr_1))
    ); // (*)
    cout << dual_ptr_1.use_count() << endl;  // 1
    cout << dual_ptr_2.use_count() << endl;  // 1
    cout << dual_ptr_1->use_count() << endl; // 1
    cout << dual_ptr_2->use_count() << endl; // 1
} // <- Double free runtime error at closing brace.

// Replacing line (*) with:
// shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
//     new shared_ptr<Wrapper>(&(**dual_ptr_1))
// );
// has much the same effect, possibly just without compiler optimization.

案例B是案例A的错误变体,其中案例B的区别在于第二个内部指针是从指向Wrapper对象的原始指针构建的,而不是从第一个内部复制构造或分配指针。因此,内部指针的use_count保持在1(而不是2),即使它们都指向同一地址。因此,这些内部指针中的每一个都表现得好像它是唯一一个管理Wrapper对象的指针。在main()的右括号中发生双重自由运行时错误,因为最后一个超出范围的内部指针试图释放已经被前一个内部指针超出范围释放的内存。

这里案例C - 两个外部指针指向指向不同Wrapper个对象的不同内部指针:

int main() {
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
        make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
    );
    shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
        make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(**dual_ptr_1))
    );
    cout << dual_ptr_1.use_count() << endl;  // 1
    cout << dual_ptr_2.use_count() << endl;  // 1
    cout << dual_ptr_1->use_count() << endl; // 1
    cout << dual_ptr_2->use_count() << endl; // 1
}

虽然Wrapper个对象具有相同的值,但它们是不同的对象,并且它们位于不同的地址。第二个内部指针的Wrapper对象是从第一个内部指针的Wrapper对象复制而成。