unique_ptr <t> .get()方法在使用原始指针分配时调用析构函数?</t>

时间:2014-08-28 05:09:35

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

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

我在第二种方法中的意图是,如何将智能指针与现有代码库一起使用,以便我可以利用智能指针提供的自动内存管理。因此,我没有更改声明中的指针类型(.i.e。从Widget* wstd::unique_ptr<Widget> 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();
}

3 个答案:

答案 0 :(得分:3)

在第二种情况下,您正在创建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();
然后,

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

答案 1 :(得分:1)

也许你误解了一点unique_ptr。它不是帮助使用原始指针的内存管理的方法,而是替换原始指针。在您的两个示例中,您都会回到get()上致电unique_ptr以获取原始poitner并使用它。这不是必需的。除了复制它并调用unique_ptr之外,您可以使用delete用于处理原始指针的所有操作。

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

作为必然结果,临时unique_ptr几乎没用,除非您使用它们初始化另一个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。人们对如何以及何时使用像auto和统一初始化这样的C ++ 11特性提出了不同的建议,所以这里有一些在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<WindowsButton>,但这通常不会受到影响,因为如果需要,它可以转换为std::unique_ptr<Widget>

答案 2 :(得分:0)

在这一行中,

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

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

w->draw();

那时,w是一个悬垂的指针。