<T>unique_ptr.get() 方法在使用原始指针赋值时调用析构函数?

unique_ptr<T>.get() method call destructor while assigning with raw pointer?

本文关键字:原始 指针 析构函数 调用 赋值 gt lt unique get ptr 方法      更新时间:2023-10-16

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

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

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

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

当然,当实例被销毁时,unique_ptrdelete托管对象,并调用~Widget()

w->draw();

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

也许您误解了unique_ptr。这不是帮助管理原始指针的内存,而是替换原始指针。在这两个例子中,您都可以在unique_ptr上调用get()来获得原始poitner并使用它。这是不需要的。除了复制和调用delete之外,您可以使用unique_ptr执行与原始指针相同的操作。

你在第二个例子中遇到的问题是一个寿命问题。由于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>

在这一行中,

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

CCD_ 29是临时对象并且在该行执行完成时被破坏。因此,Widget也被破坏。这解释了当你调用时的分段违规

w->draw();

此时,w是一个悬空指针。