何时在C++中使用new?

6

何时使用 "new" 创建类实例的良好策略是什么?我已经从事C++业余编程一段时间了,但我仍然不确定什么时候最好这样做:

MyClass thing(param1, param2);

关于这个问题:

MyClass* thing;
thing = new MyClass(param1, param2);

有什么建议吗?


是的。我的建议是阅读这本书:http://www2.research.att.com/~bs/3rd.html,特别是第5章:指针、数组和结构体。 - Assaf Lavie
2
你首先明白为什么需要动态创建对象吗? - Paul Richter
6个回答

21

在设计方面,尽可能使用自动(堆栈)分配。每当需要将对象的生存期延长超过一定范围时,请动态地对其进行分配。

即使如此,也永远不要直接进行动态分配。始终将它们包装到某种实现作用域绑定资源管理(SBRM)的包装器中,首次以愚蠢/笨拙的名称资源获取是初始化(RAII)而闻名。也就是说,动态分配应该保留在自动对象中,这些对象将自动清理!

一个很好的例子是 std::vector:您无法泄漏 vector 中的内存,因为其析构函数在应该释放内存的每种情况下都会运行,并且它将为您释放该内存。auto_ptr 是标准库中可用的第一个和唯一的智能指针,但它非常糟糕。最好使用 shared_ptr 或许多其他流行的智能指针,这些指针可在 Boost 和/或 TR1 和/或 C++0x 中找到。

在性能方面,堆栈上分配的对象可以非常快速地完成(堆栈大小按函数调用增加,因此所有所需内存已经通过简单的指针移动预先分配)。相反,动态分配通常需要更多时间。可以通过自定义分配方案实现快速动态分配,但即使是最好的方案也比堆栈分配慢得多。

偶尔,您可能会发现花费太多时间在对象复制上。在这种情况下,值得动态地进行分配并仅移动指针。但请注意,我说的是“找到”。这种更改是通过分析和测量而不是猜测来找到的。

因此:尽可能使用自动分配,必要时使用动态分配。


2
+1 特别是对于称 RAII 为愚蠢和笨拙的名称,它确实如此。 - Dan

5
第一种方法在堆栈上创建一个本地实例,当调用函数退出时该实例将消失。第二种方法创建的实例会一直留在堆上,直到(如果)您明确释放它。选择取决于您想要为对象提供什么样的控制和生命周期。

2

一个经验法则是:如果不使用new也能正常工作,就不要使用new


3
这是个不好的想法。在传递指针时,它可能看起来可以正常运行,但当栈帧被释放时,你会突然得到一个指向空指针的引用。调试已经很困难了,不要再添加试错代码。 - Borealid
但是在 MyClass thing(param1, param2); 中没有指针参与。如果你可以像使用 int 一样使用对象,那么你应该这样做。 - David Titarenco
3
如果一个人不知道栈和堆的区别(正如这个问题所表明的那样),并开始“试错编码”,他很可能发现可以返回&myStackobj,而且似乎能够工作。 - Javier

2
通常情况下:如果您计划在同一个作用域内删除对象,则不需要使用 new。 如果对象很大,您可能想使用 new
如果您想了解详细信息,可以查看堆和栈内存之间的区别。

1

首先,问问自己一个问题,当另一个函数需要它时,复制对象是否有意义?

如果复制对象有意义,最好的方法是在堆栈上创建所有内容或作为成员变量,然后在需要时只需传递副本。

如果复制对象没有意义,您将需要使用new表单,以便可以安全地传递指向对象的指针。您必须使用指针(或引用),因为如上所述,复制对象没有意义。

我知道有两个例外:

如果您知道对象在当前函数完成后不会被使用,可以在堆栈上创建对象,以便删除它。只要确保之后没有人持有指向它的指针即可!(我很少发现这种情况,但它确实会发生)

如果对象由另一个类在内部使用,该类本身不应该被复制,则可以将其放入成员变量中。由于包含它的对象不会被复制,并且仅供内部使用,因此这将是安全的。


0
 MyClass thing(param1, param2); //memory for thing is allocated on the process stack(static allocation)

 MyClass* thing;
 thing = new MyClass(param1, param2); //memory is allocated dynamically on the heap(free store) for thing

区别在于这里:

 int main()
 { 
   {
    MyClass thing(param1, param2); //thing is local to the scope
   } //destructor called for thing
   //cannot access thing (thing doesn't exist)
 } 

 int main()
 {
   {
     MyClass* thing;
     thing = new MyClass(param1, param2);
   }
  //the object pointed to by thing still exists
   //Memory leak
 }  

对于大型对象,您必须动态分配内存(使用new),因为进程堆栈具有有限的大小。

好的,作用域结束后你仍然无法访问thing。:) 它仍然存在,但由于没有指针指向它,它永远不会被释放。 - GManNickG
@GMan:是的,你说得对。我的帖子需要进行一些小的修正。 - Prasoon Saurav
1
thing指向的MyClass*将不再存在于您声明它的位置,因为它是一个局部变量,已经超出了作用域。 MyClass对象——thing曾经指向的对象,将持久存在,并且正是这个对象导致了内存泄漏。 - Larry Wang
1
@Kaestur:已编辑。该死!我需要一杯咖啡。 :-) - Prasoon Saurav
2
不是很有帮助。它给人的印象是你应该明确地使用 new。总是将其隐藏在 RAII 包装器后面,这样当包装器超出范围时它就会被删除。 - jalf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接