在使用 std::unique_ptr 在退出作用域块时自动释放内存的情况下,为啥不直接使用堆栈呢?
Posted
技术标签:
【中文标题】在使用 std::unique_ptr 在退出作用域块时自动释放内存的情况下,为啥不直接使用堆栈呢?【英文标题】:In the case of using a std::unique_ptr to automatically deallocate memory upon exiting a scoped block, why not just use the stack?在使用 std::unique_ptr 在退出作用域块时自动释放内存的情况下,为什么不直接使用堆栈呢? 【发布时间】:2020-06-10 18:27:09 【问题描述】:这是关于智能指针的一个很好的答案,例如唯一指针:What is a smart pointer and when should I use one?。
这是他们提供的一个示例,作为唯一指针的最简单用法:
void f()
std::unique_ptr<MyObject> ptr(new MyObject(my_constructor_param));
ptr->DoSomethingUseful();
// ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
然而,这引出了一个问题:在这种情况下,目标是简单地删除对象(释放内存),当它超出范围时,唯一指针指向的对象,为什么不把整个对象放在而是堆栈,像这样??
void f()
MyObject myobj(my_constructor_param);
myobj.DoSomethingUseful();
// myobj goes out of scope --
// and is automatically destroyed.
// myobj.Oops(); // Compile error: "myobj" not defined
// since it is no longer in scope.
在我看来,唯一的逻辑可能是某些对象太臭了,它们可能会溢出堆栈,因为堆栈似乎限制在几十 KB 到几 MB (@987654322 @),而一个堆可能有数百 GB!
逻辑是什么?在这里给我一些关于唯一指针这个看似不必要的用例的见解。我错过了什么?
相关:
-
“要记住的堆栈的另一个特性是,可以存储在堆栈上的变量的大小是有限制的(因操作系统而异)。分配在堆上的变量不是这种情况。 " (https://gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html)
【问题讨论】:
这不是unique_ptr
的好用例。考虑一下您想要返回动态分配的对象,或者您想要将动态分配的对象存储在容器中的情况。它对多态类型特别有用,我猜这是将它用作堆栈对象的用例。
unique_ptr
的一个很好的用例是,如果它没有在编译时定义,是否以及要分配多少对象。
为了捍卫您复制的答案,该示例是为了演示智能指针的工作原理,而不是演示典型的用例。阅读开头“智能指针应该优先于原始指针。如果您觉得需要使用指针(首先考虑是否真的这样做),您通常会想要使用智能指针,因为这可以缓解原始指针的许多问题,主要是忘记删除对象,泄露内存。”所以前提是您已经决定需要智能指针而不是原始指针
btw MyObject() myobj;
是错字,不是吗?
再想一想,asnwer 的问题是“我什么时候应该使用智能指针?”,所以确实不是最好的例子
【参考方案1】:
虽然这本身并不是一个糟糕的有用示例,但它会产生一些细微的变化。
-
多态性
struct Base void blah() std::cout << "Base\n";;
struct Derived : Base void blah() std::cout << "Derived\n";;
void blub(bool which)
std::unique_ptr<Base> ptr = which ? new Base : new Derived;
ptr->blah();
-
非标准删除器
auto close = [] (FILE* fp) fclose(fp);;
std::unique_ptr<FILE, decltype(close)> ptr(fopen("name"), close);
// closes file
-
动态数组(您可以使用向量)
std:: unique_ptr<int[]> ptr( new int [n]);
// From C++14 on, prefer if it is no problem to value-initialize the array
auto ptr = std::make_unique<int[]>(n);
// From C++20 on, there is no reason for the naked new
auto ptr = std::make_unique_for_overwrite<int[]>(n);
// is equivalent to the first line
编辑:这对于大数组也是合理的,即使在编译时知道大小。如果数组的大小真的很大(这应该非常非常罕见)并且您真的不想冒堆栈溢出的风险,那么这将是一种更安全的可能性。但很可能std::vector
仍然是更好的选择。只有当您的对象类型既不可移动也不可复制时,向量才会出现问题(因为它无法在必要时重新分配自身,因此您基本上可以调用不修改成员函数)。
-
有条件地创建对象。 (仅在 C++11 和 14 中,之后使用
std::optional
)
void blah (bool smth)
std::unique_ptr<T> opt;
if (smth)
opt = std::unique_ptr<T>(new T);
【讨论】:
好例子,我会在你有条件地创建对象实例时添加一个 仅在 C++11 和 14 中。之后我肯定会使用可选的。 很好,请修正语法,opt = new T;
无法编译,可能 opt = std::make_uniquestd::optional
不是智能指针的替代品,因为它将对象始终存储在堆栈上,没有动态分配。一旦你使用动态分配,它就不是std::optional
我是对你的例子的补充,我的理解是如果静态分配非常大的对象数组会溢出堆栈,但对于动态分配的大型对象数组则不会,这仍然是正确的,对吧?您没有在回答中提到这一点。【参考方案2】:
大小不是主要问题,尽管如果你有递归,它可能很重要,例如(我看到一个库在递归函数中在堆栈上分配 64 KiB 缓冲区。但 Musl 提供 128 KiB 堆栈 [使线程轻量级],所以...)但是对象可能是多态的,甚至不是“就地”创建的,而是从某个函数(作为指针)返回的; unique_ptr
可能很方便存储它。
除此之外,unique_ptr
(与 auto_ptr
AFAIK 不同)不限于堆栈使用。它也可以是类成员。也不需要实际存储任何东西,您可以随时分配和重置它。
此外,它不限于 C++ 类,您可以在其中存储任何需要清理的内容,例如文件描述符或 FILE*。
【讨论】:
【参考方案3】:如果你想返回一个指向对象的指针,那么在没有智能指针的情况下,我们必须这样做:
MyClass* someFunc()
MyClass* myObjPtr = new MyClass();
.
.
return myObjPtr;
void otherFunc()
MyClass* myPtr = someFunc();
.
.
delete myPtr; // delete explicitely before return.
return;
如果我们使用智能指针,我们可以这样做:
typedef std::shared_ptr<MyClass> MyClassPtr;
MyClassPtr someFunc()
MyClassPtr myObjPtr(new MyClass());
.
.
return myObjPtr;
void otherFunc()
MyClassPtr myPtr = someFunc();
.
.
return; // when smart pointer goes out of scope,
// the heap allocated object which it contains is deallocated properly.
【讨论】:
以上是关于在使用 std::unique_ptr 在退出作用域块时自动释放内存的情况下,为啥不直接使用堆栈呢?的主要内容,如果未能解决你的问题,请参考以下文章