当我可以在析构函数中销毁对象时,为什么要使用std :: unique_ptr?

ar1*_*r1a 2 c++ pointers destructor unique-ptr

说我有这个班:

class Foo
{
public:
    Foo()
    {
        bar = new Bar;
    }

    ~Foo()
    {
        if(bar)
            delete bar;
    }
private:
    Bar* bar;

};
Run Code Online (Sandbox Code Playgroud)

为什么我要std::unique_ptrunique_ptr膨胀时使用而不是原始指针?有什么优势吗?有没有我的析构函数不会被调用的情况?

tem*_*def 31

您上面的代码实际上有一个错误,因为您没有定义复制构造函数或赋值运算符.想象一下这段代码:

Foo one;
Foo two = one;
Run Code Online (Sandbox Code Playgroud)

因为twoone它的副本,所以使用默认的复制构造函数初始化它 - 这使得两个bar指针指向同一个对象.这意味着当用于two触发的析构函数时,它将释放共享的同一个对象one,因此one析构函数将触发未定义的行为.哎呀.

现在,如果你不想让你的对象可以复制,你可以明确地这样说:

class Foo
{
public:
    Foo()
    {
        bar = new Bar;
    }
    Foo(const Foo&) = delete;
    Foo& operator= (const Foo&) = delete;

    ~Foo()
    {
        if(bar)
            delete bar;
    }
private:
    Bar* bar;

};
Run Code Online (Sandbox Code Playgroud)

所以这解决了这个问题 - 但看看涉及的代码量!您必须明确删除两个函数并手动编写析构函数.

除了另一个问题.假设我这样做:

Foo one;
Foo two = std::move(one);
Run Code Online (Sandbox Code Playgroud)

这个初始化two通过移动内容onetwo.或者是吗?不幸的是,答案是否定的,因为默认的移动构造函数将默认移动指针,这只是一个直指针复制.所以现在你得到了和以前一样的东西.哎呀.

不用担心!我们可以通过定义自定义移动构造函数和移动赋值运算符来解决此问题:

class Foo
{
public:
    Foo()
    {
        bar = new Bar;
    }
    Foo(const Foo&) = delete;
    Foo& operator= (const Foo&) = delete;

    Foo(Foo&& rhs)
    {
        bar = rhs.bar;
        rhs.bar = nullptr;
    }

    Foo& operator= (Foo&& rhs)
    {
        if (bar != rhs.bar)
        {
            delete bar;
            bar = rhs.bar;
            rhs.bar = nullptr;
        }
    }

    ~Foo()
    {
        if(bar)
            delete bar;
    }
private:
    Bar* bar;

};
Run Code Online (Sandbox Code Playgroud)

唷!这是一大堆代码,但至少它是正确的.(或者是吗?)

另一方面,想象你写了这个:

class Foo
{
public:
    Foo() : bar(new Bar) {
    }
private:
    std::unique_ptr<Bar> bar;
};
Run Code Online (Sandbox Code Playgroud)

哇,这短得多!它会自动确保类不能被复制,并且它使得默认移动构造函数和移动赋值操作符正常工作.

因此,一个巨大的优势std::unique_ptr是它会自动处理资源管理,是的,但另一个优点是它可以很好地处理复制和移动语义,并且不会以意想不到的方式工作.这是使用它的主要原因之一.你可以说出你的意思 - "我是唯一应该了解这件事的人,你不能分享它" - 编译器会为你强制执行它.争取编译器帮助您避免错误几乎总是一个好主意.

至于臃肿 - 我需要看到证据.std::unique_ptr是一个指针类型的薄包装器,一个好的优化编译器应该没有问题为它生成良好的代码.确实,有与之相关的构造函数,析构函数等std::unique_ptr,但是合理的编译器会内联这些调用,这实际上与您最初描述的内容完全相同.

  • 如果我笑的话,还可以.只是一点点?"但我的例子很容易"......然而海报仍然搞砸了. (6认同)
  • @Snorflake我假设你的朋友也使用原始`char*`并在每次需要时编写自己的动态数组实现.您使用标准库是因为它们解决了问题.为什么每次编写新程序时都要自己解决? (6认同)
  • 你的朋友对日常问题的简单解决方案有一种奇怪的厌恶. (6认同)
  • @Snorflake:"*因为我懒得避免使用STL*"有充分的理由避免使用标准库的任何特定元素.但要声称使用良好,经过良好测试且非常有用的代码是*懒惰*是荒谬的.这个人似乎没有理由,所以我建议你不要听他们的建议. (6认同)
  • @Snorflake我不确定你朋友的意思是什么.我不认为上述答案与懒惰有任何关系.(另外,要真正迂腐,但希望以一种有教育意义的方式:术语"STL"通常指的是像`std :: vector`和`std :: map`这样的东西,它们最初是名为STL的库的一部分,但它有因为已经将(部分)合并到语言中.我不会将`std :: unique_ptr`称为STL的一部分.) (5认同)
  • @Snorflake`const char*`s不能作为`std :: map`中的键工作的原因实际上真的很有启发性,并解释了为什么`std :: string`是一个很好的说谎的原因之一周围.看看你是否能在网站上找到一个很好的解释! (5认同)
  • @Snorflake:不要接受这位朋友的建议,他们对软件有害.`std :: map`适用于`const char*`就好了.也许他们想将他们的`const char*`视为C字符串,然后他们认为它不起作用?除了它仍然存在,只要你提供一个`Compare`类来处理`const char*`作为C字符串(你知道,因为语言不是魔术,没有理由对待每个`const char*`作为以null结尾的字符串,因此你需要告诉它).等等.这个人不知道他们在做什么. (2认同)