【问题标题】:unique_ptr<T>.get() method call destructor while assigning with raw pointer?unique_ptr<T>.get() 方法在使用原始指针分配时调用析构函数?
【发布时间】:2014-10-21 20:41:50
【问题描述】:

下面的程序使用std::unique_ptr&lt;T&gt; 来避免手动内存管理。我尝试了两种方法来实现它。问题在于第二种方法,在分配给原始指针之前,析构函数被调用。这会导致程序崩溃,因为后面的代码会尝试访问无效内存。

我在第二种方法中的意图是,如何在现有代码库中使用智能指针,以便我可以利用智能指针提供的自动内存管理。因此我没有更改声明中的指针类型(即从Widget* wstd::unique_ptr&lt;Widget&gt; w)。

有人可以详细解释一下吗?最佳实践应该是什么?还是我遗漏了什么?

#include<iostream>
#include<memory>
class Widget {
public:
    Widget() { std::cout << "Widget::Widget()" << std::endl; }
    virtual ~Widget() { std::cout << "Widget::~Widget()" << std::endl; }
    virtual void draw() = 0;
};

class WindowsButton : public Widget {
public:
    WindowsButton() = default;
    ~WindowsButton() = default;
    void draw() { std::cout << "WindowsButton"<<std::endl; }
};

int main() {    
    // Working Code
    // std::unique_ptr<Widget> w = std::unique_ptr<Widget>(new WindowsButton());
    // w.get()->draw();

    //In this way program is crashing while calling the w->draw()
    Widget* w = std::unique_ptr<Widget>(new WindowsButton()).get();
    w->draw();
}

【问题讨论】:

  • 简单地将w 变成std::unique_ptr&lt;Widget&gt; 有什么问题?
  • @0x499602D2:改原型没问题。但正如我在 SO 中提到的那样,我想在现有代码库中执行此操作。
  • 第一种情况下不需要w.get()-&gt;draw();unique_ptr 提供了operator-&gt;() 重载,所以w-&gt;draw(); 有效。
  • 你必须构造一个std::unique_ptr,然后调用get()

标签: c++ pointers c++11 crash unique-ptr


【解决方案1】:

在第二种情况下,您正在创建 unique_ptr 的临时实例并在其上调用 get() 成员函数。 unique_ptr 对象将在完整表达式的末尾(分号处)销毁。

Widget* w = std::unique_ptr<Widget>(new WindowsButton()).get();
//          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^      ^
//          unnamed temporary instance                        destroyed here

当然,unique_ptr 将在实例被销毁时将delete 托管对象调用~Widget()

w->draw();

然后将取消引用指向无效内存位置的指针,从而导致未定义的行为。

【讨论】:

    【解决方案2】:

    也许你对unique_ptr有点误解。这不是帮助原始指针内存管理的方法,而是替换原始指针。在您的两个示例中,您都回退到在您的unique_ptr 上调用get() 以获取原始指针并使用它。那是不需要的。您可以使用 unique_ptr 来完成您对原始指针所做的所有操作,除了复制它并调用 delete

    您在第二个示例中遇到的问题是生命周期问题。由于unique_ptr 拥有它指向的对象,因此它控制了它的生命周期,这意味着当unique_ptr 被销毁时,该对象必须被销毁。由于unique_ptr 是临时的,它会破坏同一行中的对象。

    因此,临时的unique_ptrs 几乎没有什么用处,除非您使用它们来初始化另一个unique_ptrshared_ptr 或其他一些接管所有权并因此将unique_ptr 留空的对象。

    在以下几行中,我将稍微完善您的“工作代码”:

    std::unique_ptr<Widget> w = std::unique_ptr<Widget>(new WindowsButton());
    w.get()->draw();
    

    这在安全和语义上是可以的,你在这里没有做坏事。但正如我所说,对get() 的调用是不必要的,显式复制初始化也是如此:

    std::unique_ptr<Widget> w(new WindowsButton());
    w->draw();
    

    这是 C++03 风格的使用方式,也适用于 C++11。人们对如何以及何时使用 C++11 功能(如 auto 和统一初始化)给出了不同的建议,因此这里有一些在 C++11 中会更好/更好的示例:

    std::unique_ptr<Widget> w{new WindowsButton{}};
    

    甚至

    auto w{std::unique_ptr<Widget>{new WindowsButton{}}};
    

    (我认为这很丑)。

    在 C++14 中,有 make_unique 和几乎从不使用 new 的建议,因此您的初始化将如下所示:

    auto w{std::make_unique<WindowsButton>()};
    

    这里,w 的类型为 std::unique_ptr&lt;WindowsButton&gt;,但这通常不会受到影响,因为如果需要它可以转换为 std::unique_ptr&lt;Widget&gt;

    【讨论】:

      【解决方案3】:

      在这一行,

      Widget* w = std::unique_ptr<Widget>(new WindowsButton()).get();
      

      unique_ptr 是一个临时对象,在行完成执行时会被破坏。因此,Widget 也会被破坏。这解释了您调用时的分段违规

      w->draw();
      

      当时w是一个悬空指针。

      【讨论】:

        猜你喜欢
        • 2019-12-13
        • 1970-01-01
        • 1970-01-01
        • 2021-07-15
        • 2023-01-26
        • 1970-01-01
        • 2015-05-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多