假设我有这个类:
class Foo
{
public:
Foo()
{
bar = new Bar;
}
~Foo()
{
if(bar)
delete bar;
}
private:
Bar* bar;
};
当 unique_ptr
膨胀时,为什么我要使用 std::unique_ptr
而不是原始指针?有什么优势吗?是否存在我的析构函数不会被调用的情况?
最佳答案
您上面的代码实际上有一个错误,因为您没有定义复制构造函数或赋值运算符。想象一下这段代码:
Foo one;
Foo two = one;
因为 two
是 one
的拷贝,它使用默认的复制构造函数进行初始化 - 这使得两个 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;
};
这样就解决了这个问题——但是看看涉及的代码量!您必须显式删除两个函数并手动编写析构函数。
除了还有另一个问题。假设我这样做:
Foo one;
Foo two = std::move(one);
这通过将 one
的内容移动到 two
来初始化 two
。或者是吗?不幸的是,答案是否定的,因为默认的移动构造函数将默认移动指针,它只是直接复制指针。所以现在你得到了和以前一样的东西。糟糕。
不用担心!我们可以通过定义自定义移动构造函数和移动赋值运算符来解决此问题:
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;
};
呸!这是一大堆 代码,但至少它是正确的。 (或者是吗?)
另一方面,假设您这样写:
class Foo
{
public:
Foo() : bar(new Bar) {
}
private:
std::unique_ptr<Bar> bar;
};
哇,短多了!它会自动确保类不能被复制,并且它使默认的移动构造函数和移动赋值运算符正常工作。
所以 std::unique_ptr
的一个巨大优势是它自动处理资源管理,是的,但另一个优势是它可以很好地处理复制和移动语义并且不会以意想不到的方式工作.这是使用它的主要原因之一。你可以说出你的意思——“我是唯一应该知道这件事的人,你不能分享它”——编译器会为你强制执行。让编译器帮你避免错误几乎总是一个好主意。
至于膨胀 - 我需要看到这方面的证据。 std::unique_ptr
是一个指针类型的薄包装器,一个好的优化编译器应该可以毫不费力地为它生成好的代码。的确,存在与 std::unique_ptr
关联的构造函数、析构函数等,但合理的编译器将内联这些调用,它们基本上只是做与您最初描述的相同的事情。
关于c++ - 当我可以在我的析构函数中销毁对象时,为什么我应该使用 std::unique_ptr?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37370732/