将shared_ptr的嵌套智能指针重置为shared_ptr(或unique_ptr),看起来很矛盾



我知道std::shared_ptr管理的对象不是delete d by reset(),除非它是当时唯一管理它的shared_ptr。我知道,当有多个shared_ptr管理同一对象时,被管理对象的值的更改会通过指向它的所有shared_ptr反映出来,而由reset()改变其引起的对这些shared_ptr的值中的任何一个的改变(而不是其托管对象的值)(即将shared_ptr从指向原始托管对象的CCD_9改变为不指向任何东西或其他东西的CCD_ 9)不会改变其他shared_ptr的值(即它们都仍然指向原始受管对象,并且原始托管对象仍然存在):

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

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

我的猜测是:outer shared_ptrs中的任何一个的托管对象都是内部shared_ptr(而不是Wrapper),并更改为内部shared_ptr的值(通过reset()更改内部shared_ptr,将内部shared_ptr的值从指向Wrapper实例的值更改为不指向任何实例的值)反映在所有outer shared_ptrs中,有效地导致alloutershared_ptrs失去对Wrapper实例的间接管理,从而删除Wrapper实例。

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

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

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
}

显然存在指向1 Wrapper对象的2内部指针;内部指针的CCD_ 48最多为CCD_;CCD_ 50类的CCD_;并且间接管理的CCD_ 53对象可以通过两个外部指针访问(这意味着两个外部指示器都没有被另一个构造的移动);并且重置一个内部指针有效地重置所有内部指针;所以我仍然不理解这个看似矛盾的现象。

我在这篇文章中也提出了同样的问题,关于上面的代码将内部shared_ptrs替换为unique_ptrs,内部make_shared替换为make_unique,并且内部指针的use_count()被注释掉的情况(因为unique_ptrs缺少该方法),这会给出相同的输出。对我来说,这似乎是一个悖论,因为unique_ptr在这里似乎并不独特。

给定一个名为Wrappershared_ptr<shared_ptr<Wrapper>>的类任何数量的其他shared_ptr<shared_ptr<Wrapper>>初始化为以前,为什么这种配置允许在任何内部共享ptr以有效地CCD_ 65所有其它内部CCD_?

没有其他内部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

接受的答案用图表显示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称为第一内部指针。我将CCD_ 75称为第二外部指针,将它所指向的CCD_。两个外部指针指向不同的内部指针。第二个外部指针没有被复制构造或分配给第一个外部指针,因此任何一个外部指针的use_count都是1。第二个外部指针不指向第一个内部指针,而是一个由第一个内部指示器复制而成的无名内部指针。虽然第二个外部指针仍在管理第二个内部指针,但后者的匿名性不会导致后者超出范围。第二内部指针指向与第一内部指针相同的Wrapper,因为第二内部指示器是从第一内部指针复制构建的。由于这种shared_ptr复制构造,任何一个内部指针的use_count都是2。为了销毁Wrapper,每个内部指针都必须是reset(),不指向任何内容或其他内容,分配给其他内容,或者超出范围(只要每个内部指针至少经过其中一个,则不需要两者都经过相同的操作)。

这是另一个案例,案例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对象复制构建的。

最新更新