当返回可能在 C++ 中的堆栈上分配的成员时,最好的指针/引用类型是啥?
Posted
技术标签:
【中文标题】当返回可能在 C++ 中的堆栈上分配的成员时,最好的指针/引用类型是啥?【英文标题】:What is best pointer/reference type when returning member possibly allocated on the stack in C++?当返回可能在 C++ 中的堆栈上分配的成员时,最好的指针/引用类型是什么? 【发布时间】:2019-01-31 18:43:26 【问题描述】:为了说明我的问题,我举了一个例子:
#include <iostream>
using namespace std;
struct A
void doSomething ()
cout << "Something\n";
;
struct B
A a;
A *getA ()
return &a;
;
int
main ()
B *b = new B ();
A *a = b->getA ();
// POINT 1
if (nullptr != a)
a->doSomething ();
delete b;
b = nullptr;
// POINT 2
if (nullptr != a)
a->doSomething ();
return 0;
这在我的机器上编译和运行没有错误,但是如果您检查代码,确实存在标记“POINT 2”的注释后面的行上的悬空指针的问题。
由于b
被删除,那么a
现在无效(因为它被b
的dtor删除了)。
所以我可以使用共享指针来解决这个问题,但是即使在 b
被删除之后,a
的实例也会保留,而且我也无法在堆栈上分配 a
。这是我想要避免的两件事。相反,我只是想知道a
是否仍然有效。
我也可以使用唯一指针,但我只能拥有一个 a
的单个实例,这也不是我想要的,我想要指向 a
的指针的多个副本。
那么是否有一些现有的指针/引用类型可以让我这样做?有什么理由说明这是一个好/坏的主意吗?
【问题讨论】:
根本不要使用原始指针。而是使用smart pointers 来明确实例的所有权。 没有快速简便的解决方法。如果您想知道a
的有效性状态,您必须将该信息保存在某处。一个对象不能告诉你它生命周期的当前状态,它需要活着才能询问它的当前状态。
“我可以使用共享指针来解决这个问题,但是即使在 b 被删除之后,a 的实例也会保留”如果 getA()
返回 weak_ptr<A>
则不会。
如果您希望a
共享b
的a
的所有权,那么您需要shared_ptr
,否则您可以使用weak_ptr
【参考方案1】:
您刚刚发现了所有权语义的奇妙之处:)
如何解决这个问题取决于您的应用程序的设计:您需要什么以及您想要实现什么。
在这种情况下,如果你真的想共享一个对象的所有权,请使用std::shared_ptr
,它会保留剩余多少指针的引用计数,以便最后删除该对象;可能是std::weak_ptr
,如果您只需要检查对象是否还活着,但又不想让它存活的时间超过需要的时间。
但是,请注意(ab)使用共享指针可能是糟糕设计的标志。
顺便说一句,您的A a;
成员没有在堆栈中分配(即标题错误)。
【讨论】:
是的,我想使用 RIIA,但仍然能够安全地访问课堂外的成员。也许那是糟糕的设计:/ @LennartRolland 在这种情况下,只需将成员放入std::shared_ptr
。如果您真的想避免分配/间接,请按照@Slava 的建议进行。但是,是的,我会认真重新考虑设计。大多数事情都可以在不依赖共享所有权的情况下解决。
你说得对,A 没有在堆栈上分配 - 它分配在 B 被分配的地方,对吗?所以它可以在堆栈中分配,但在这个例子中没有。
@LennartRolland 事实上,a
是B
的成员,所以无论您分配B
的实例,该成员都会在哪里。在这种情况下,既然你写了new B
,那就是空闲存储(堆)。
忘记堆栈和堆。而是讨论存储持续时间。【参考方案2】:
唯一想到的使用标准库的可行解决方案是使用std::weak_ptr() - 它允许在不持有其所有权的情况下检查对象的有效性。这伴随着价格 - 您必须使用 std::shared_ptr
来维护它的所有权。虽然可以为具有自动存储持续时间和 noop 删除器的对象创建std::shared_ptr
,但我只会在我真的需要时这样做,因为这样的方法容易出错并且违背了智能指针的目的。
【讨论】:
@Acorn 我明白了,我提到了 OP 担心使用std::shared_ptr
会消除创建具有自动存储持续时间(即在堆栈上)的对象的能力。
是的,我理解得太晚了(删除评论),对不起。
@Acorn 没什么好遗憾的【参考方案3】:
最好的办法是不暴露a
。
您的B
是接口。给它你需要执行的功能。让它继续调用它需要在a
上调用的任何东西,以实现这一点。
然后删除getA()
,并将a
设为私有。
现在它被完全封装了,调用范围不能像这样乱搞!
不需要指针或动态分配;很好,老式的 OOP。
【讨论】:
以上是关于当返回可能在 C++ 中的堆栈上分配的成员时,最好的指针/引用类型是啥?的主要内容,如果未能解决你的问题,请参考以下文章