10

我知道这可能被解释为“您的偏好是什么”问题之一,但我真的很想知道为什么您会选择以下方法中的一种而不是另一种。

假设您有一个超级复杂的类,例如:


class CDoSomthing {

    public:
        CDoSomthing::CDoSomthing(char *sUserName, char *sPassword)
        {
            //Do somthing...
        }

        CDoSomthing::~CDoSomthing()
        {
            //Do somthing...
        }
};

我应该如何在全局函数中声明本地实例?


int main(void)
{
    CDoSomthing *pDoSomthing = new CDoSomthing("UserName", "Password");

    //Do somthing...

    delete pDoSomthing;
}

- 或者 -


int main(void)
{
    CDoSomthing DoSomthing("UserName", "Password");

    //Do somthing...

    return 0;
}
4

7 回答 7

27

首选局部变量,除非您需要对象的生命周期超出当前块。(局部变量是第二种选择)。这比担心内存管理要容易得多。

PS如果你需要一个指针,因为你需要它传递给另一个函数,只需使用地址操作符:

SomeFunction(&DoSomthing);
于 2009-02-21T04:18:26.120 回答
22

当您在堆栈和堆中声明变量时,有两个主要考虑因素 - 生命周期控制和资源管理。

当您严格控制对象的生命周期时,在堆栈上进行分配非常有效。这意味着您不会将该对象的指针或引用传递给本地函数范围之外的代码。这意味着,没有输出参数,没有 COM 调用,没有新线程。相当多的限制,但是您可以在正常或异常退出当前范围时为您正确清理对象(尽管,您可能希望阅读使用虚拟析构函数的堆栈展开规则)。堆栈分配的最大缺点 - 堆栈通常被限制为 4K 或 8K,因此您可能要小心放在上面的内容。

另一方面,在堆上分配将需要您手动清理实例。这也意味着您有很大的自由来控制实例的生命周期。您需要在两种情况下执行此操作:a)您将要将该对象传递到范围之外;或者 b) 对象太大并且在堆栈上分配它可能会导致堆栈溢出。

顺便说一句,这两者之间的一个很好的折衷方案是在堆上分配对象并在堆栈上分配一个智能指针。这可确保您不会浪费宝贵的堆栈内存,同时仍会在范围退出时进行自动清理。

于 2009-02-21T04:32:38.800 回答
13

第二种形式是所谓的 RAII(资源获取即初始化)模式。它比第一个有很多优点。

使用时new,必须delete自己使用,并保证即使抛出异常,也会一直被删除。你必须自己保证这一切。

如果使用第二种形式,当变量超出范围时,它总是会自动清理。如果抛出异常,堆栈会展开并且也会被清理。

因此,您应该更喜欢 RAII(第二个选项)。

于 2009-02-21T04:23:23.300 回答
6

除了到目前为止所说的之外,还需要考虑其他性能注意事项,特别是在内存分配密集型应用程序中:

  1. 使用new将从堆中分配内存。在密集(极其频繁)分配和释放的情况下,您将在以下方面付出高昂代价:
    • 锁定:堆是进程中所有线程共享的资源。堆上的操作可能需要锁定堆管理器(在运行时库中为您完成),这可能会显着减慢速度。
    • 碎片:堆碎片。您可能会看到 malloc/new 和 free/delete 返回所需的时间增加了 10 倍。这与上面的锁定问题更加复杂,因为管理碎片堆需要更多时间,并且更多线程排队等待修复锁。(在 Windows 上,您可以为堆管理器设置一个特殊标志,以便它试探性地尝试减少碎片。)
  2. 使用 RAII 模式,只需将内存从堆栈中取出。堆栈是每个线程的资源,它不会分段,不涉及锁定,并且可能会在内存局部性(即 CPU 级别的内存缓存)方面发挥您的优势。

因此,当您在短时间内(或范围内)需要对象时,一定要使用第二种方法(局部变量,在堆栈上)。如果您需要在线程之间共享数据,请使用new/malloc(一方面你必须,在另一方面,这些对象的寿命通常足够长,因此您对堆管理器的成本基本上是 0。)

于 2009-02-21T04:43:21.910 回答
3

如果抛出异常,第二个版本将展开堆栈。第一个不会。否则我看不出有太大的不同。

于 2009-02-21T04:20:34.710 回答
2

两者最大的区别在于new初始化了一个指向对象的指针。

通过创建没有new的对象,启动的对象存储在堆栈上。如果它是用 new 启动的,它会返回一个指向在堆上创建的新对象的指针。它实际上返回一个指向新对象的内存地址。发生这种情况时,您需要对变量进行内存管理。使用完变量后,您需要对其调用 delete 以避免内存泄漏。如果没有 new 运算符,当变量超出范围时,内存将被自动释放。

因此,如果您需要将变量传递到当前范围之外,使用 new 会更有效。但是,如果您需要创建一个临时变量,或者只是临时使用的东西,那么将对象放在堆栈上会更好,因为您不必担心内存管理。

于 2009-02-21T05:36:50.520 回答
0

newMark Ransom 是对的,如果要将变量作为参数传递给 CreateThread-esque 函数,则还需要实例化。

于 2009-02-21T04:21:41.933 回答