【问题标题】:Error spotted in C++ Primer 5th edition shared_ptr<int>C++ Primer 第 5 版 shared_ptr<int> 中发现错误
【发布时间】:2021-06-05 11:41:19
【问题描述】:

您好,我正在阅读 C++ 入门第 5 版,我想我在 shared_ptr 部分下发现了一个错误。首先,我正在编写代码和他们给出的解释。然后我会写出我认为是错误的内容以及我认为实际发生的事情。代码如下:

shared_ptr<int> p(new int(42));// reference count is 1
int *q = p.get();// ok: but don't use q in any way that might delete its pointer
{//new block started
    shared_ptr<int>(q);
}// block ends, q is destroyed, and the memory to which q points is freed
int foo = *p;// undefined; the memory to which p points was freed

他们给出的解释如下:

在这种情况下,p 和 q 都指向同一个内存。因为它们是相互独立创建的,所以每个引用计数为 1。当定义 q 的块结束时,q 被销毁。销毁 q 会释放 q 指向的内存。这使得 p 成为一个悬空指针,这意味着当我们尝试使用 p 时发生的事情是未定义的。此外,当 p 被销毁时,指向该内存的指针将被第二次删除。

现在我认为错误是语句“当定义 q 的块结束时,q 被销毁。销毁 q 释放 q 指向的内存。”以及他们给出的推理为什么 p 是一个悬空指针的背后是错误的。以下是我为什么 p 是一个悬空指针以及为什么第一个引用的语句是错误的原因。

  1. 当定义 q 的块结束时,q 被销毁。但是q 指向的内存没有被释放,因为q 是内置指针而不是shared_ptr。除非我们显式地写 delete q,否则相应的内存不会被释放。
  2. 现在,在新块中,我们使用 q 创建了一个临时 shared_ptr。但是这个临时的独立于p。所以当这个内部块结束时,临时被破坏,因此内存被释放。但请注意,p 仍然指向已释放的同一内存。所以p 现在是一个悬空指针,在int foo=*p 语句中使用p 是未定义的。

我认为这是对为什么 p 是悬空指针的正确解释,也是应该存在的更正。有人可以确认这是对的还是我做错了什么?

【问题讨论】:

  • 你说的是对的。该代码可以很好地演示 p 最终悬空,但本书的措辞有点误导,因为它将 q 本身描述为 shared_ptr 而不是从 q 的内存中创建 shared_ptr正在指向。
  • 2 个不同的共享指针(不同的控制块)被创建,拥有相同的原始指针。当其中一个被摧毁时,另一个被悬空。
  • 我认为这是书中的一个错字,内块应该是std::shared_ptr&lt;int&gt; q(p.get());(而不是重用外块的名称)。
  • 注:第 12.1.3 节(第 466 页)

标签: c++ c++11 c++14 shared-ptr errata


【解决方案1】:

C++ Primer 5th edition 以及您的解释在试图解释这个程序时犯了一个常见错误。注意声明:

shared_ptr<int>(q);

创建一个名为q 的新临时文件,而不是使用q 作为shared_ptr 构造函数的参数创建一个新临时文件。下面的示例代码显示了这一点:

#include <iostream>

using namespace std;
struct NAME
{
    int p = 0;
    NAME()
    {
        std::cout<<"default constructor"<<std::endl;
    }
    NAME(int d): p(d)
    {
        std::cout<<"d: "<<d<<" p: "<<p<<std::endl;
       
        
    }
    NAME(const NAME& n)
    {
        std::cout<<"const copy constructor"<<std::endl;
    }
    NAME(NAME& n)
    {
        std::cout<<"non const copy constructor"<<std::endl;
    }
    ~NAME(){
    std::cout<<"destructor: "<<p<<std::endl;
    
    }
};
int main()
{
   cout << "Hello World" << endl; 
   NAME (4);//calls converting constructor
   //after the completion of the above full statement the temporary is destroyed and hence you get a destructor call in the output
   
   NAME k;//calls default constructor
   std::cout<<"k.p: "<<k.p<<std::endl;
   
   NAME(l);//this creates a temporary named l instead of creating a temporary using l as parameter to NAME's constructor ( in particular,default constructor)
    
   NAME{l};//this creates a temporary using l as parameter to NAME's constructor with non-const copy constructor
    

   
   return 0;
}

你的解释的第二点是正确的只有我们使用shared_ptr&lt;int&gt;{q};而不是shared_ptr&lt;int&gt;(q);

这意味着:

  1. 由于作者使用了shared_ptr&lt;int&gt;(q);,因此创建了一个名为q 的局部变量,它是一个智能指针,而不是来自外部作用域的内置指针。

  2. 这个局部变量q 与外部作用域的pq无关,因此当这个局部变量q 超出作用域时,会调用shared_ptr 的析构函数。但请注意外部pq 指向的内存未释放

  3. 所以之后当我们写int foo = *p; 时,没有未定义的行为。

下面是code,它显示shared_ptr&lt;int&gt;(q); 定义了一个名为q 的本地而不是使用q 作为参数的临时。

#include <iostream>
#include <memory>
using namespace std;

int main()
{
   cout << "Hello World" << endl; 
  
   
shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get();  // ok: but don't use q in any way that might delete its pointer
std::cout<<"q's address "<<&q<<std::endl;
std::cout<<"p's address "<<&p<<std::endl;
    { // new block
    
    shared_ptr<int>(q);
    std::cout<<"new q's address "<<&q<<std::endl;
    std::cout<<"new q's value "<<(*q)<<std::endl;//this produces segmentation fault
} // block ends, local is destroyed
int foo = *p; // this is ok

   return 0;
}

在上面的代码中,如果我们尝试使用*q 访问本地q 的值,那么我们将得到未定义的行为(这可能会导致程序/分段错误崩溃),因为我们正在取消引用一个空指针。如果我们删除这个*q,那么程序就没有未定义的行为。

即使在本书的下一版(第 6 版)中,作者也使用 auto local = shared_ptr&lt;int&gt;(q);,而他本可以使用 shared_ptr&lt;int&gt;{q}; 来表达自己的观点。

【讨论】:

    【解决方案2】:

    正如您正确指出的那样,代码中的文本描述和 cmets 都不适合代码。它们更符合这样的代码:

    shared_ptr<int> p(new int(42));// reference count is 1
    {//new block started
        shared_ptr<int> q(p.get());
    }// block ends, q is destroyed, and the memory to which q points is freed
    int foo = *p;// undefined; the memory to which p points was freed
    

    如果我猜的话,我会说这就是示例最初的样子,然后有人决定引入原始指针,却没有意识到它还需要更改 cmets 和文本。

    【讨论】:

      【解决方案3】:

      在第六次印刷中,您指出的内容略有不同,如下:

      shared_ptr<int> p(new int(42)); // reference count is 1
      int *q = p.get();  // ok: but don't use q in any way that might delete its pointer
      { // new block
          // undefined: two independent shared_ptrs point to the same memory
          auto local = shared_ptr<int>(q);
      } // block ends, local is destroyed, and the memory to which p and  q points is freed
      int foo = *p; // undefined; the memory to which p points was freed
      

      这里,pqlocal 都指向同一个内存。因为plocal 是相互独立创建的,所以每个引用计数为1。当内部块结束时,local 被销毁。因为local的引用计数为1,它指向的内存将被释放。这使得pq 成为悬空指针;当我们尝试使用pq 时会发生什么是未定义的。此外,当p被销毁时,指向该内存的指针将被第二次删除。

      我认为author's errata page 不再受到管理。

      【讨论】:

        猜你喜欢
        • 2022-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多