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

mac*_*low 1 c++ heap-memory

考虑一个像这样的类

class Element{
    public:
    // approx. size of ~ 2000 Byte
    BigStruct aLargeMember;

    // some method, which does not depend on aLargeMember
    void someMethod();
}
Run Code Online (Sandbox Code Playgroud)

现在假设 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();
}
Run Code Online (Sandbox Code Playgroud)

基本上,这对应于/sf/answers/2565259441/中给出的建议 4 :

仅当有明确需要时才使用 new,例如:

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

    • 如果这是您使用动态分配的唯一原因,并且您确实希望对象的生命周期与函数中的作用域相关联,则应该使用本地 std::unique_ptr<> 来管理动态内存,并确保它无论您如何离开作用域,都会被释放:通过 return、 throw、break 等。(您还可以在类/结构中使用 std::unique_ptr<> 数据成员来管理对象拥有的任何内存。)

所以,我的问题是:在当前情况下,堆方法是否被认为是不好的做法?或者在本例中是否有任何反对堆的好的论据?先感谢您!

Rei*_*ica 5

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

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

class Element{
    public:
    // approx. size of ~ 2000 Byte
    BigStruct aLargeMember;

    // some method, which does not depend on aLargeMember
    void someMethod();
}
Run Code Online (Sandbox Code Playgroud)

那么这里没有在堆栈上分配任何内容。

由于 aLargeMember 并不经常需要,并且考虑到 Element 的实例数量较多,动态创建 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();
};
Run Code Online (Sandbox Code Playgroud)

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

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

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