C++类成员:堆栈与堆分配

C++ Class Member: Stack vs. Heap Allocation

本文关键字:分配 堆栈 成员 C++      更新时间:2023-10-16

考虑一个像这样的类

class Element{
public:
// approx. size of ~ 2000 Byte
BigStruct aLargeMember;
// some method, which does not depend on aLargeMember
void someMethod();
}

现在假设 Element 的许多实例(例如,运行时有 100,000,000 个实例,同时存在大约 50,000 个实例(是在运行时创建的,并且通常只调用someMethod(),而无需为aLargeMember分配内存。 (这个说明性示例派生自非线性有限元代码,类Element实际上表示有限元。

现在我的问题:由于aLargeMember不是经常需要的,并且考虑到大量的Element实例,动态创建aLargeMember是否有利?例如

class Element{
public:
// approx. size of ~ 2000 Byte
std::unique_ptr<BigStruct> aLargeMember;
// only called when aLargeMember is needed
void initializeALargeMember{
aLargeMember = std::unique_ptr<BigStruct>( new BigStruct() );}
// some method, which does not depend on aLargeMember
void someMethod();
}

基本上,这对应于 https://stackoverflow.com/a/36646563/4859499 中给出的建议4:

仅在有明确需求时才使用 new,例如:

  • 一个特别大的分配,会占用大部分堆栈(您的操作系统/进程将"协商"一个限制,通常在 1-8+ 兆字节范围内(

    • 如果这是使用动态分配的唯一原因,并且确实希望对象的生存期与函数中的范围相关联, 您应该使用本地 std::unique_ptr<> 来管理动态 内存,并确保无论您如何离开范围,它都已释放: 通过返回,投掷,打破等。(您也可以使用 std::unique_ptr<> 类/结构中的数据成员,用于管理对象拥有的任何内存。

所以,我的问题是:本案中的堆方法是否被认为是不好的做法?或者在本案中是否有任何反对堆的好论据?提前谢谢你!

C++类成员:堆栈与堆分配

不要将基于堆栈的分配与具有堆分配对象的成员变量混淆。如果您有此类的堆分配对象:

class Element{
public:
// approx. size of ~ 2000 Byte
BigStruct aLargeMember;
// some method, which does not depend on aLargeMember
void someMethod();
}

然后,堆栈上不会分配此处的任何内容。

由于 aLargeMember 不是经常需要的,并且考虑到 元素的大量实例,是否有利于 动态创建大型成员?

您描述的场景确实是动态内存分配的合法候选者,因为不这样做只会使您分配比您需要的更多,而没有收益 - 因此您没有太多其他选择。

本案中的堆方法是否被视为不良做法?

这个问题有点太笼统了,很容易被解释为基于意见,但在给定代码段的上下文中,您似乎指的是根据需要使用分配成员变量的场景,因此进行延迟初始化可能更可取,以减轻必须手动维护代码中每个点的初始化的开销。为此,您可以包装对此成员的访问,以确保返回已初始化的内容。只是为了说明,非常不线程安全,这个想法是这样的:

class Element{
private:
// approx. size of ~ 2000 Byte
std::unique_ptr<BigStruct> aLargeMember;
// A wrapper through which you access aLargeMember
BigStruct& GetLargeMember()
{
if (!aLargeMember)
{
aLargeMember = std::make_unique<BigStruct>();
}
return *aLargeMember;
}
public:
// some method, which might depend on aLargeMember
void someMethod();
};

如果您需要传递它以在类范围之外使用,那么您将面临悬而未决的引用的危险,因为拥有unique_ptr的已分配实例可能已经被销毁。如果是这种情况,并且您真的想保证不要这样做,那么unique_ptr不合适,因为您只能移动它。相反,请考虑使用shared_ptr并从GetLargeMember()返回实际的智能指针。

或者在本案中是否有任何反对堆的好论据?

不反对在这里使用堆,但至少有几种模式可用于处置。例如,鉴于您打算创建如此大量的实例,但同时存在的实例要少得多,我会认真考虑class Element的实例。