我为什么/不应该使用“new”运算符来实例化一个类,为什么?

时间:2009-02-21 04:14:06

标签: c++ class instantiation

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

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


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;
}

7 个答案:

答案 0 :(得分:27)

首选局部变量,除非您需要将对象的生命周期延伸到当前块之外。 (局部变量是第二种选择)。这比担心内存管理更容易。

P.S。如果你需要一个指针,因为你需要它传递给另一个函数,只需使用address-of运算符:

SomeFunction(&DoSomthing);

答案 1 :(得分:22)

在堆栈上声明变量与堆中的变量(生命周期控制和资源管理)时,有两个主要注意事项。

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

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

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

答案 2 :(得分:13)

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

使用new时,您必须自己使用delete,并保证始终删除它,即使引发了异常。你必须自己保证这一切。

如果使用第二种形式,当变量超出范围时,它将始终自动清除。如果抛出异常,堆栈会展开并清理它。

所以,你应该更喜欢RAII(第二种选择)。

答案 3 :(得分:6)

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

  1. 使用new将从堆中分配内存。在激烈(极其频繁)分配和解除分配的情况下,您将付出高昂的代价:
    • locked:堆是进程中所有线程共享的资源。堆上的操作可能需要在堆管理器中锁定(在运行时库中为您完成),这可能会显着降低速度。
    • 碎片:堆碎片。您可能会看到malloc / new和free / delete返回的时间增加了10倍。这与上面的锁定问题有关,因为它需要更多的时间来管理碎片堆,并且更多线程排队等待愈合锁定。 (在Windows上,您可以为堆管理器设置一个特殊标志,以便启发式地尝试减少碎片。)
  2. 使用RAII模式,只需将内存从堆栈中取出。堆栈是一个每线程资源,它没有碎片,没有涉及锁定,并且可能在内存局部性方面发挥优势(即CPU级别的内存缓存。)
  3. 因此,当您需要短暂(或范围)时间段的对象时,请务必使用第二种方法(堆栈上的局部变量)。如果需要在线程之间共享数据,请使用new/malloc(一方面,你必须要做的是,这些对象通常具有足够长的使用寿命,因此与堆管理器相比,你支付的成本基本上是0成本。)

答案 4 :(得分:3)

如果抛出异常,第二个版本将展开堆栈。第一个不会。我没有看到太多不同。

答案 5 :(得分:2)

两者之间最大的区别是新的启动指向对象的指针。

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

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

答案 6 :(得分:0)

Mark Ransom是对的,如果你要将变量作为参数传递给CreateThread-esque函数,你还需要用new进行实例化。