为什么 C++ 程序员应该尽量减少“new”的使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6500313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why should C++ programmers minimize use of 'new'?
提问by bitgarden
I stumbled upon Stack Overflowquestion Memory leak with std::string when using std::list<std::string>, and one of the commentssays this:
我在使用 std::list<std::string> 时偶然发现了堆栈溢出问题Memory leak with std::string,其中一条评论说:
Stop using
new
so much. I can't see any reason you used new anywhere you did. You can create objects by value in C++and it's one of the huge advantages to using the language.
You do not have to allocate everything on the heap.
Stop thinking like a Javaprogrammer.
别用
new
那么多了。我看不出你在任何地方使用 new 的任何理由。您可以在C++ 中按值创建对象,这是使用该语言的巨大优势之一。
您不必在堆上分配所有内容。
停止像Java程序员那样思考。
I'm not really sure what he means by that.
我不太确定他的意思是什么。
Why should objects be created by value in C++as often as possible, and what difference does it make internally?
Did I misinterpret the answer?
为什么在C++ 中应该尽可能多地按值创建对象,它在内部有什么区别?
我误解了答案吗?
回答by André Caron
There are two widely-used memory allocation techniques: automatic allocation and dynamic allocation. Commonly, there is a corresponding region of memory for each: the stack and the heap.
有两种广泛使用的内存分配技术:自动分配和动态分配。通常,每个都有对应的内存区域:堆栈和堆。
Stack
堆
The stack always allocates memory in a sequential fashion. It can do so because it requires you to release the memory in the reverse order (First-In, Last-Out: FILO). This is the memory allocation technique for local variables in many programming languages. It is very, very fast because it requires minimal bookkeeping and the next address to allocate is implicit.
堆栈总是以顺序方式分配内存。它可以这样做,因为它要求您以相反的顺序(先进后出:FILO)释放内存。这是许多编程语言中局部变量的内存分配技术。它非常非常快,因为它需要最少的簿记,并且要分配的下一个地址是隐式的。
In C++, this is called automatic storagebecause the storage is claimed automatically at the end of scope. As soon as execution of current code block (delimited using {}
) is completed, memory for all variables in that block is automatically collected. This is also the moment where destructorsare invoked to clean up resources.
在 C++ 中,这称为自动存储,因为在作用域结束时自动声明存储。当前代码块(使用 分隔{}
)的执行完成后,将自动收集该块中所有变量的内存。这也是调用析构函数来清理资源的时刻。
Heap
堆
The heap allows for a more flexible memory allocation mode. Bookkeeping is more complex and allocation is slower. Because there is no implicit release point, you must release the memory manually, using delete
or delete[]
(free
in C). However, the absence of an implicit release point is the key to the heap's flexibility.
堆允许更灵活的内存分配模式。簿记更复杂,分配更慢。因为没有隐式释放点,你必须手动释放内存,使用delete
or delete[]
(free
在 C 中)。然而,没有隐式释放点是堆灵活性的关键。
Reasons to use dynamic allocation
使用动态分配的原因
Even if using the heap is slower and potentially leads to memory leaks or memory fragmentation, there are perfectly good use cases for dynamic allocation, as it's less limited.
即使使用堆较慢并且可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较少。
Two key reasons to use dynamic allocation:
使用动态分配的两个关键原因:
You don't know how much memory you need at compile time. For instance, when reading a text file into a string, you usually don't know what size the file has, so you can't decide how much memory to allocate until you run the program.
You want to allocate memory which will persist after leaving the current block. For instance, you may want to write a function
string readfile(string path)
that returns the contents of a file. In this case, even if the stack could hold the entire file contents, you could not return from a function and keep the allocated memory block.
您不知道在编译时需要多少内存。例如,将文本文件读入字符串时,通常不知道文件的大小,因此在运行程序之前无法决定分配多少内存。
您想分配在离开当前块后将持续存在的内存。例如,您可能想要编写一个
string readfile(string path)
返回文件内容的函数。在这种情况下,即使堆栈可以保存整个文件内容,您也无法从函数返回并保留分配的内存块。
Why dynamic allocation is often unnecessary
为什么动态分配通常是不必要的
In C++ there's a neat construct called a destructor. This mechanism allows you to manage resources by aligning the lifetime of the resource with the lifetime of a variable. This technique is called RAIIand is the distinguishing point of C++. It "wraps" resources into objects. std::string
is a perfect example. This snippet:
在 C++ 中有一个简洁的构造,称为析构函数。这种机制允许您通过将资源的生命周期与变量的生命周期对齐来管理资源。这种技术称为RAII,是 C++ 的区别点。它将资源“包装”到对象中。 std::string
是一个完美的例子。这个片段:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
actually allocates a variable amount of memory. The std::string
object allocates memory using the heap and releases it in its destructor. In this case, you did notneed to manually manage any resources and still got the benefits of dynamic memory allocation.
实际上分配了可变数量的内存。该std::string
对象使用堆分配内存并在其析构函数中释放它。在这种情况下,你是不是需要手动管理的任何资源,还是把动态内存分配的好处。
In particular, it implies that in this snippet:
特别是,它意味着在这个片段中:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
there is unneeded dynamic memory allocation. The program requires more typing (!) and introduces the risk of forgetting to deallocate the memory. It does this with no apparent benefit.
有不需要的动态内存分配。该程序需要更多的输入 (!) 并带来忘记释放内存的风险。它这样做没有明显的好处。
Why you should use automatic storage as often as possible
为什么应该尽可能多地使用自动存储
Basically, the last paragraph sums it up. Using automatic storage as often as possible makes your programs:
基本上,最后一段总结了它。尽可能多地使用自动存储使您的程序:
- faster to type;
- faster when run;
- less prone to memory/resource leaks.
- 打字更快;
- 运行时更快;
- 不太容易发生内存/资源泄漏。
Bonus points
奖励积分
In the referenced question, there are additional concerns. In particular, the following class:
在引用的问题中,还有其他问题。特别是以下类:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Is actually a lot more risky to use than the following one:
实际上比以下使用风险要大得多:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
The reason is that std::string
properly defines a copy constructor. Consider the following program:
原因是std::string
正确定义了一个复制构造函数。考虑以下程序:
int main ()
{
Line l1;
Line l2 = l1;
}
Using the original version, this program will likely crash, as it uses delete
on the same string twice. Using the modified version, each Line
instance will own its own string instance, each with its own memory and both will be released at the end of the program.
使用原始版本,这个程序可能会崩溃,因为它delete
在同一个字符串上使用了两次。使用修改后的版本,每个Line
实例将拥有自己的字符串实例,每个实例都有自己的内存,并且都将在程序结束时释放。
Other notes
其他注意事项
Extensive use of RAIIis considered a best practice in C++ because of all the reasons above. However, there is an additional benefit which is not immediately obvious. Basically, it's better than the sum of its parts. The whole mechanism composes. It scales.
由于上述所有原因,广泛使用RAII被认为是 C++ 中的最佳实践。然而,还有一个额外的好处不是立即显而易见的。基本上,它比其各部分的总和要好。整个机制组成。它缩放。
If you use the Line
class as a building block:
如果您使用Line
该类作为构建块:
class Table
{
Line borders[4];
};
Then
然后
int main ()
{
Table table;
}
allocates four std::string
instances, four Line
instances, one Table
instance and all the string's contents and everything is freed automagically.
分配四个std::string
实例,四个Line
实例,一个Table
实例和所有字符串的内容,所有内容都自动释放。
回答by DigitalRoss
Because the stack is faster and leak-proof
因为堆栈更快且防漏
In C++, it takes but a single instruction to allocate space -- on the stack -- for every local scope object in a given function, and it's impossible to leak any of that memory. That comment intended (or should have intended) to say something like "use the stack and not the heap".
在 C++ 中,只需要一条指令就可以为给定函数中的每个局部作用域对象在堆栈上分配空间,并且不可能泄漏任何内存。该评论打算(或应该打算)说诸如“使用堆栈而不是堆”之类的内容。
回答by Nicol Bolas
The reason why is complicated.
原因很复杂。
First, C++ is not garbage collected. Therefore, for every new, there must be a corresponding delete. If you fail to put this delete in, then you have a memory leak. Now, for a simple case like this:
首先,C++ 不是垃圾收集的。因此,对于每一个new,都必须有一个对应的delete。如果你没有把这个删除放进去,那么你就有了内存泄漏。现在,对于这样的简单案例:
std::string *someString = new std::string(...);
//Do stuff
delete someString;
This is simple. But what happens if "Do stuff" throws an exception? Oops: memory leak. What happens if "Do stuff" issues return
early? Oops: memory leak.
这很简单。但是如果“做东西”抛出异常会发生什么?糟糕:内存泄漏。如果“做东西”return
提早出现问题会怎样?糟糕:内存泄漏。
And this is for the simplest case. If you happen to return that string to someone, now they have to delete it. And if they pass it as an argument, does the person receiving it need to delete it? When should they delete it?
这是最简单的情况。如果您碰巧将该字符串返回给某人,现在他们必须将其删除。如果他们将它作为参数传递,接收它的人是否需要删除它?他们应该什么时候删除它?
Or, you can just do this:
或者,您可以这样做:
std::string someString(...);
//Do stuff
No delete
. The object was created on the "stack", and it will be destroyed once it goes out of scope. You can even return the object, thus transfering its contents to the calling function. You can pass the object to functions (typically as a reference or const-reference: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
. And so forth.
没有delete
。该对象是在“堆栈”上创建的,一旦超出范围,它将被销毁。您甚至可以返回对象,从而将其内容传输给调用函数。您可以将对象传递给函数(通常作为引用或常量引用:void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)
. 等等。
All without new
and delete
. There's no question of who owns the memory or who's responsible for deleting it. If you do:
所有没有new
和delete
。谁拥有内存或谁负责删除它是毫无疑问的。如果你这样做:
std::string someString(...);
std::string otherString;
otherString = someString;
It is understood that otherString
has a copy of the dataof someString
. It isn't a pointer; it is a separate object. They may happen to have the same contents, but you can change one without affecting the other:
据了解,otherString
有一份数据的副本someString
。它不是一个指针;它是一个单独的对象。它们可能碰巧具有相同的内容,但您可以更改其中一个而不影响另一个:
someString += "More text.";
if(otherString == someString) { /*Will never get here */ }
See the idea?
看到这个想法了吗?
回答by Seva Alekseyev
Objects created by new
must be eventually delete
d lest they leak. The destructor won't be called, memory won't be freed, the whole bit. Since C++ has no garbage collection, it's a problem.
创建的对象new
最终必须被delete
删除,以免泄漏。不会调用析构函数,不会释放内存,整个位。由于 C++ 没有垃圾收集,这是一个问题。
Objects created by value (i. e. on stack) automatically die when they go out of scope. The destructor call is inserted by the compiler, and the memory is auto-freed upon function return.
按值创建的对象(即在堆栈上)在超出范围时会自动死亡。析构函数调用由编译器插入,并在函数返回时自动释放内存。
Smart pointers like unique_ptr
, shared_ptr
solve the dangling reference problem, but they require coding discipline and have other potential issues (copyability, reference loops, etc.).
智能指针一样unique_ptr
,shared_ptr
解决了悬挂引用问题,但它们需要编码规则,并有其他潜在问题(可拷贝性,参考循环等)。
Also, in heavily multithreaded scenarios, new
is a point of contention between threads; there can be a performance impact for overusing new
. Stack object creation is by definition thread-local, since each thread has its own stack.
此外,在多线程场景中,new
线程之间是一个争用点;过度使用可能会对性能产生影响new
。根据定义,堆栈对象的创建是线程本地的,因为每个线程都有自己的堆栈。
The downside of value objects is that they die once the host function returns - you cannot pass a reference to those back to the caller, only by copying, returning or moving by value.
值对象的缺点是一旦宿主函数返回它们就会消失——你不能将这些对象的引用传回给调用者,只能通过复制、返回或按值移动。
回答by sarat
- C++ doesn't employ any memory manager by its own. Other languages like C#, Java has garbage collector to handle the memory
- C++ implementations typically use operating system routines to allocate the memory and too much new/delete could fragment the available memory
- With any application, if the memory is frequently being used it's advisable to pre-allocate it and release when not required.
- Improper memory management could lead memory leaks and it's really hard to track. So using stack objects within the scope of function is a proven technique
- The downside of using stack objects are, it creates multiple copies of objects on returning, passing to functions etc. However smart compilers are well aware of these situations and they've been optimized well for performance
- It's really tedious in C++ if the memory being allocated and released in two different places. The responsibility for release is always a question and mostly we rely on some commonly accessible pointers, stack objects (maximum possible) and techniques like auto_ptr (RAII objects)
- The best thing is that, you've control over the memory and the worst thing is that you will not have any control over the memory if we employ an improper memory management for the application. The crashes caused due to memory corruptions are the nastiest and hard to trace.
- C++ 本身不使用任何内存管理器。其他语言如 C#、Java 有垃圾收集器来处理内存
- C++ 实现通常使用操作系统例程来分配内存,过多的新建/删除可能会导致可用内存碎片化
- 对于任何应用程序,如果内存经常被使用,建议预先分配它并在不需要时释放。
- 不正确的内存管理可能会导致内存泄漏,而且很难跟踪。所以在函数范围内使用堆栈对象是一种行之有效的技术
- 使用堆栈对象的缺点是,它在返回、传递给函数等时创建对象的多个副本。 然而,智能编译器很清楚这些情况,并且它们已经针对性能进行了很好的优化
- 如果在两个不同的地方分配和释放内存,在 C++ 中真的很乏味。释放的责任始终是一个问题,大多数情况下我们依赖于一些通常可访问的指针、堆栈对象(最大可能)和像 auto_ptr(RAII 对象)这样的技术
- 最好的事情是,您可以控制内存,而最糟糕的是,如果我们对应用程序采用不正确的内存管理,您将无法控制内存。由于内存损坏而导致的崩溃是最严重且难以追踪的。
回答by Emily L.
I see that a few important reasons for doing as few new's as possible are missed:
我发现忽略了尽可能少做新的几个重要原因:
Operator new
has a non-deterministic execution time
运算符new
具有不确定的执行时间
Calling new
may or may not cause the OS to allocate a new physical page to your process this can be quite slow if you do it often. Or it may already have a suitable memory location ready, we don't know. If your program needs to have consistent and predictable execution time (like in a real-time system or game/physics simulation) you need to avoid new
in your time critical loops.
调用new
可能会也可能不会导致操作系统为您的进程分配一个新的物理页面,如果您经常这样做,这可能会很慢。或者它可能已经准备好了合适的内存位置,我们不知道。如果您的程序需要具有一致且可预测的执行时间(例如在实时系统或游戏/物理模拟中),您需要避免new
时间关键循环。
Operator new
is an implicit thread synchronization
运算符new
是隐式线程同步
Yes you heard me, your OS needs to make sure your page tables are consistent and as such calling new
will cause your thread to acquire an implicit mutex lock. If you are consistently calling new
from many threads you are actually serialising your threads (I've done this with 32 CPUs, each hitting on new
to get a few hundred bytes each, ouch! that was a royal p.i.t.a. to debug)
是的,您听到了,您的操作系统需要确保您的页表一致,因此调用new
将导致您的线程获取隐式互斥锁。如果你一直new
从多个线程调用,你实际上是在序列化你的线程(我已经用 32 个 CPU 完成了这个,每个 CPU 都new
获得了几百个字节,哎哟!这是一个要调试的皇家皮塔)
The rest such as slow, fragmentation, error prone, etc have already been mentioned by other answers.
其他答案已经提到了其他问题,例如缓慢、碎片化、容易出错等。
回答by user541686
Pre-C++17:
C++17 之前:
Because it is prone to subtle leaks even if you wrap the result in a smart pointer.
因为即使您将结果包装在智能指针中,它也容易出现细微的泄漏。
Consider a "careful" user who remembers to wrap objects in smart pointers:
考虑一个“细心”的用户,他记得用智能指针包装对象:
foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));
This code is dangerous because there is no guaranteethat either shared_ptr
is constructed beforeeither T1
or T2
. Hence, if one of new T1()
or new T2()
fails after the other succeeds, then the first object will be leaked because no shared_ptr
exists to destroy and deallocate it.
此代码是危险的,因为有没有保证,要么shared_ptr
被构建之前无论是T1
或T2
。因此,如果其中一个new T1()
或new T2()
失败后另一个成功,那么第一个对象将被泄漏,因为不shared_ptr
存在销毁和释放它。
Solution: use make_shared
.
解决方法:使用make_shared
.
Post-C++17:
C++17 后:
This is no longer a problem: C++17 imposes a constraint on the order of these operations, in this case ensuring that each call to new()
must be immediately followed by the construction of the corresponding smart pointer, with no other operation in between. This implies that, by the time the second new()
is called, it is guaranteed that the first object has already been wrapped in its smart pointer, thus preventing any leaks in case an exception is thrown.
这不再是一个问题:C++17 对这些操作的顺序施加了限制,在这种情况下,确保每次调用之后new()
必须紧跟相应智能指针的构造,中间没有其他操作。这意味着,在new()
调用第二个对象时,可以保证第一个对象已经包含在其智能指针中,从而在抛出异常时防止任何泄漏。
A more detailed explanation of the new evaluation order introduced by C++17 was provided by Barry in another answer.
Barry在另一个答案中提供了对 C++17 引入的新评估顺序的更详细解释。
Thanks to @Remy Lebeaufor pointing out that this is stilla problem under C++17 (although less so): the shared_ptr
constructor can fail to allocate its control block and throw, in which case the pointer passed to it is not deleted.
感谢@Remy Lebeau指出这在 C++17 下仍然是一个问题(尽管不那么严重):shared_ptr
构造函数可能无法分配其控制块并抛出,在这种情况下传递给它的指针不会被删除。
Solution: use make_shared
.
解决方法:使用make_shared
.
回答by Charlie Martin
To a great extent, that's someone elevating their own weaknesses to a general rule. There's nothing wrong per sewith creating objects using the new
operator. What there is some argument for is that you have to do so with some discipline: if you create an object you need to make sure it's going to be destroyed.
在很大程度上,这是将自己的弱点提升为一般规则的人。使用操作符创建对象本身并没有错new
。有一些争论是你必须遵守一些纪律:如果你创建一个对象,你需要确保它会被销毁。
The easiest way of doing that is to create the object in automatic storage, so C++ knows to destroy it when it goes out of scope:
最简单的方法是在自动存储中创建对象,所以 C++ 知道当它超出范围时销毁它:
{
File foo = File("foo.dat");
// do things
}
Now, observe that when you fall off that block after the end-brace, foo
is out of scope. C++ will call its dtor automatically for you. Unlike Java, you don't need to wait for the GC to find it.
现在,请注意,当您在大括号后从该块上掉下来时,foo
超出了范围。C++ 会自动为你调用它的 dtor。与 Java 不同,您不需要等待 GC 找到它。
Had you written
你写过吗
{
File * foo = new File("foo.dat");
you would want to match it explicitly with
你想明确地将它与
delete foo;
}
or even better, allocate your File *
as a "smart pointer". If you aren't careful about that it can lead to leaks.
或者甚至更好,将您分配File *
为“智能指针”。如果你不小心,它可能会导致泄漏。
The answer itself makes the mistaken assumption that if you don't use new
you don't allocate on the heap; in fact, in C++ you don't know that. At most, you know that a small amout of memory, say one pointer, is certainly allocated on the stack. However, consider if the implementation of File is something like
答案本身做出了错误的假设,即如果不使用new
,则不会在堆上分配;事实上,在 C++ 中你不知道这一点。最多,您知道在堆栈上肯定分配了一小部分内存,例如一个指针。但是,请考虑 File 的实现是否类似于
class File {
private:
FileImpl * fd;
public:
File(String fn){ fd = new FileImpl(fn);}
then FileImpl
will stillbe allocated on the stack.
然后FileImpl
将仍然可以在栈上分配。
And yes, you'd better be sure to have
是的,你最好确保有
~File(){ delete fd ; }
in the class as well; without it, you'll leak memory from the heap even if you didn't apparentlyallocate on the heap at all.
在课堂上也是如此;没有它,你会泄漏从堆内存,即使你没有明显都在堆上分配。
回答by Andrew Edgecombe
new()
shouldn't be used as littleas possible. It should be used as carefullyas possible. And it should be used as often as necessary as dictated by pragmatism.
new()
不应该尽可能少地使用。应该尽可能小心地使用它。它应该根据实用主义的要求尽可能经常使用。
Allocation of objects on the stack, relying on their implicit destruction, is a simple model. If the required scope of an object fits that model then there's no need to use new()
, with the associated delete()
and checking of NULL pointers.
In the case where you have lots of short-lived objects allocation on the stack should reduce the problems of heap fragmentation.
堆栈上的对象分配,依赖于它们的隐式销毁,是一个简单的模型。如果对象所需的范围适合该模型,则无需使用new()
, 并关联delete()
并检查 NULL 指针。在堆栈上有大量短期对象分配的情况下,应该减少堆碎片的问题。
However, if the lifetime of your object needs to extend beyond the current scope then new()
is the right answer. Just make sure that you pay attention to when and how you call delete()
and the possibilities of NULL pointers, using deleted objects and all of the other gotchas that come with the use of pointers.
但是,如果您的对象的生命周期需要超出当前范围,那么这new()
就是正确的答案。只要确保您注意调用的时间delete()
和方式以及空指针的可能性,使用已删除的对象以及使用指针带来的所有其他问题。
回答by Tim
When you use new, objects are allocated to the heap. It is generally used when you anticipate expansion. When you declare an object such as,
使用 new 时,对象被分配到堆中。它通常在您预期扩展时使用。当您声明一个对象时,例如,
Class var;
it is placed on the stack.
它被放置在堆栈上。
You will always have to call destroy on the object that you placed on the heap with new. This opens the potential for memory leaks. Objects placed on the stack are not prone to memory leaking!
您将始终必须使用 new 对放置在堆上的对象调用 destroy 。这打开了内存泄漏的可能性。放置在堆栈上的对象不容易发生内存泄漏!