V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
tracker647
V2EX  ›  问与答

C++ Primer 第五版智能指针的这两段描写是不是有误?

  •  
  •   tracker647 · 2021-12-23 20:02:17 +08:00 · 1727 次点击
    这是一个创建于 1084 天前的主题,其中的信息可能已经有所发展或是发生改变。
    p404:

    到目前为止,我们使用过的类中,分配的资源都与对应对象生存期一致。例如,每个
    vector“拥有”其自己的元素。当我们拷贝一个 vector 时,原 vector 和副本 vector
    中的元素是相互分离的:
    vector<string> v1;
    {
    vector<string> v2 = {"a","an","the"};
    v1 = v2;
    }
    由一个 vector 分配的元素只有当这个 vector 存在时才存在。当一个 vector 被销毁
    时,这个 vector 中的元素也都被销毁。

    上段字面理解了下以为是说 v1 是引用 v2 的元素,若 v2 内的元素过了块被销毁,v1 的元素也就没了,但是自己代码敲了一遍发现能正常使用:

    vector<string> v1;
    {
    vector<string> v2= {"a","an","the"};
    v1 =v2;
    }

    for(string s: v1){
    cout <s << " ";
    }

    结果:an an the

    p414
    ...也不要用 get 初始化一个智能指针或者为智能指针赋值

    智能指针类型定义了一个名为 get 的函数(参见表 12.1),它返回一个内置指针,
    指向智能指针管理的对象。此函数是为了这样一种情况而设计的:我们需要向不能使用智
    能指针的代码传递一个内置指针。使用 get 返回的指针的代码不能 delete 此指针。
    虽然编译器不会给出错误信息,但将另一个智能指针也绑定到 get 返回的指针上是
    错误的:
    shared ptr<int> p(new int (42) ); //引用计数为 1
    int *q = p.get(); 1/正确:但使用 q 时要注意,不要让它管理的指针被释放
    ( //新程序块
    //未定义:两个独立的 shared-ptr 指向相同的内存
    shared ptr<int>(q);
    //程序块结束, q 被销毁,它指向的内存被释放
    int foo = *p; //未定义: p 指向的内存已经被释放了
    }
    在本例中, p 和 q 指向相同的内存,由于它们是相互独立创建的,因此各自的引用计数都
    是 1,当 q 所在的程序块结束时, q 被销毁,这会导致 q 指向的内存被释放。从而 p 变成
    一个空悬指针,意味着当我们试图使用 p 时,将发生未定义的行为。而且,当 p 被销毁时,
    这块内存会被第二次 delete.

    这段自己也敲了代码,也是能正常引用 p:

    shared ptrcint> p3(new int(42));
    cout <<*p3 < endl;
    int *q1 =p3.get();
    {
    shared ptr<int>(q1);
    }
    int foo=p33,
    cout <<foo < endl;

    结果:
    42
    42

    是 C++更新导致书的描述过时了还是我看错了?
    第 1 条附言  ·  2021-12-23 21:06:54 +08:00
    当时的运行结果图片如下:
    ![image-20211223194415788]( https://s2.loli.net/2021/12/23/NMKiqsmI34AuCWG.png)

    ![image-20211223194729449]( https://s2.loli.net/2021/12/23/dTQ2Emr1i7qslNo.png)
    第 2 条附言  ·  2021-12-23 21:10:38 +08:00
    第一个基本懂了

    第二个听大家的分析看起来像是我遇到了编译器的某种巧合还是语法问题,我也不清楚,代码先发下:
    #include<iostream>
    #include<memory>
    using namespace std;
    void process(shared_ptr<int> ptr){

    }
    int main(){
    // //1.不要混用普通指针和智能指针 p413
    // shared_ptr<int> p1(new int(42));
    // process(p1); //函数会拷贝传入变量,计数变为 2 ,故最后内存不会被释放
    // int i = *p1;
    // cout << i << endl;

    // int *p2(new int(1024));
    // process(shared_ptr<int>(p2)); //给函数传递临时的 shared_ptr 变量不会递增其计数,最后内存会被释放
    // int j = *p2;
    // cout << j << endl;

    //2.禁用 get 给另一个智能指针赋值
    shared_ptr<int> p3(new int(42));
    cout <<*p3 << endl;
    int *q1 = p3.get();
    {
    shared_ptr<int>(q1);
    }
    int foo = *p3;
    cout << foo << endl;


    }
    9 条回复    2021-12-24 09:46:42 +08:00
    secondwtq
        1
    secondwtq  
       2021-12-23 20:33:04 +08:00
    第一个,v1 = v2 时会把 v2 里的元素拷贝一份给 v1 ,所以你 v1 是可以用的
    第二个,有两种可能,一是虽然 UB 了,但是并没出明显的错误。二是你遇到了 C 坑爹语法里的一个坑,你定义了一个名为 q1 ,类型为 shared_ptr<int>的变量,而非用 q1 初始化了一个匿名的 shared_ptr<int>值。由于你这个奇葩排版实在是难以判断,我怀疑你要是把整段程序都写出来没准还能看到什么 studio.h ,void 面函数之类的
    secondwtq
        2
    secondwtq  
       2021-12-23 20:34:30 +08:00
    另外生命周期的问题最好是搞一个有各种 ctor 和 dtor 的类,这样看着清楚一点。
    wevsty
        3
    wevsty  
       2021-12-23 20:34:32 +08:00
    1 、我认为书中的表达是没有说错的,是你理解出现了偏差。
    原书中的意思是只有 vector 还在生存周期的时候这些元素才能正常访问,如果 vector 销毁,则其中的元素应该被认为已经被销毁了。

    2 、书中的描述很明确。如果你使用 get()拿到了原始指针,并且拿原始指针初始化了另一个智能指针,在第二个智能指针退出生命周期的时候内存已经被 delete 过一次了。使用已经被 delete 过的内存是一种未定义的行为,也许编译器在处理上并没有重新使用这段内存空间,所以你仍然能访问得到原始的结果,但是这只是一种巧合(偶然),并非一定会发生的事件。

    最后友情提示:发帖请注意排版
    mmm159357456
        4
    mmm159357456  
       2021-12-23 21:02:33 +08:00
    第一个用等号是拷贝赋值,用括号同理
    tracker647
        5
    tracker647  
    OP
       2021-12-23 21:12:43 +08:00
    @secondwtq 图省事直接用 OCR 扫了书上代码处理了下。。。。想想应该直接 md 弄好代码的
    halfdb
        6
    halfdb  
       2021-12-23 22:05:30 +08:00 via Android
    第二个:
    从 C++标准的角度说,解引用已经释放的指针是未定义行为,也就是上面说的 UB 。这意味着 C++标准不规定这个行为会引发什么。所以你这一次虽然访问到了原来的数据,但是 C++不保证你每次都会得到这样的结果。

    从实现的角度来说,一个指针指向的空间被释放,并不代表那里的数据就没有了。编译器可没空帮你清空数据。在那个空间被下个使用者覆盖掉之前,如果你拿着原来的指针去访问,仍然可以获得原来的数据。
    wevsty
        7
    wevsty  
       2021-12-23 22:10:21 +08:00   ❤️ 2
    不要混用普通指针和智能指针 p413
    这个只是一个建议,并不是一个不可打破的规则。混用本身没有问题,只是你得知道自己在做什么。

    shared_ptr 是内部帮你维护了一个引用计数, 通过引用计数来确保内存不再被引用的时候就释放。而在使用过程中如果使用 get() 或者直接使用原始指针并不改变引用计数,这是需要自己注意的。
    yianing
        8
    yianing  
       2021-12-24 01:02:43 +08:00 via Android
    内存被释放了不代表里面内容会被立即清除,继续访问属于未定义行为
    aneostart173
        9
    aneostart173  
       2021-12-24 09:46:42 +08:00
    你试试看*p3 = 0 会不会出错咯,或者 delete p3 会不会报 double free error.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3107 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 04:58 · PVG 12:58 · LAX 20:58 · JFK 23:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.