当返回可能在 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&lt;A&gt; 则不会。 如果您希望a 共享ba 的所有权,那么您需要shared_ptr,否则您可以使用weak_ptr 【参考方案1】:

您刚刚发现了所有权语义的奇妙之处:)

如何解决这个问题取决于您的应用程序的设计:您需要什么以及您想要实现什么。

在这种情况下,如果你真的想共享一个对象的所有权,请使用std::shared_ptr,它会保留剩余多少指针的引用计数,以便最后删除该对象;可能是std::weak_ptr,如果您只需要检查对象是否还活着,但又不想让它存活的时间超过需要的时间。

但是,请注意(ab)使用共享指针可能是糟糕设计的标志。


顺便说一句,您的A a; 成员没有在堆栈中分配(即标题错误)。

【讨论】:

是的,我想使用 RIIA,但仍然能够安全地访问课堂外的成员。也许那是糟糕的设计:/ @LennartRolland 在这种情况下,只需将成员放入std::shared_ptr。如果您真的想避免分配/间接,请按照@Slava 的建议进行。但是,是的,我会认真重新考虑设计。大多数事情都可以在不依赖共享所有权的情况下解决。 你说得对,A 没有在堆栈上分配 - 它分配在 B 被分配的地方,对吗?所以它可以在堆栈中分配,但在这个例子中没有。 @LennartRolland 事实上,aB 的成员,所以无论您分配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++ 中的堆栈上分配的成员时,最好的指针/引用类型是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在递归函数中在堆上分配与在堆栈上分配

C++ 堆栈分配对象,显式析构函数调用

C++:编译器如何知道为每个堆栈帧分配多少内存?

对象向量是不是在 C++ 中的堆或堆栈上分配?

java内存分配(堆栈常量池)

C++中的堆栈