为什么 C++ 程序员应该尽量减少“新"的使用?
我偶然发现 Stack Overflow 问题 使用 std::list 时 std::string 的内存泄漏<std::string>,其中一条评论这样说:
I stumbled upon Stack Overflow question Memory leak with std::string when using std::list<std::string>, and one of the comments says this:
停止使用 new
这么多.我看不出你在任何地方使用 new 的任何原因你做到了.您可以在 C++ 中按值创建对象,它是其中之一使用该语言的巨大优势.
您不必分配一切都在堆上.
不要像 Java 程序员那样思考.
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 Java programmer.
我不太确定他的意思.
为什么要在 C++ 中尽可能频繁地按值创建对象,这在内部有什么不同?
我是否误解了答案?
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?
推荐答案
有两种广泛使用的内存分配技术:自动分配和动态分配.通常,每个都有一个对应的内存区域:堆栈和堆.
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.
堆栈总是按顺序分配内存.它可以这样做是因为它要求您以相反的顺序(先进后出:FILO)释放内存.这是许多编程语言中局部变量的内存分配技术.它非常非常快,因为它需要最少的簿记,并且要分配的下一个地址是隐式的.
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.
在 C++ 中,这称为 自动存储,因为存储在作用域结束时会自动声明.当前代码块(使用 {}
分隔)的执行完成后,将自动收集该块中所有变量的内存.这也是调用析构函数来清理资源的时刻.
In C++, this is called automatic storage because 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 destructors are invoked to clean up resources.
堆允许更灵活的内存分配模式.簿记更复杂,分配更慢.因为没有隐式释放点,所以必须手动释放内存,使用delete
或delete[]
(C 中的free
).然而,没有隐式释放点是堆灵活性的关键.
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.
即使使用堆的速度较慢并可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较少.
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.
您想要分配的内存在离开当前块后将持续存在.例如,您可能想要编写一个返回文件内容的函数 string readfile(string path)
.在这种情况下,即使堆栈可以保存整个文件内容,您也无法从函数返回并保留分配的内存块.
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.
在 C++ 中有一个简洁的构造,称为 析构函数.此机制允许您通过将资源的生命周期与变量的生命周期对齐来管理资源.这种技术被称为 RAII 并且是 C++ 的区别点.它包裹"了资源转化为对象.std::string
就是一个完美的例子.这个片段:
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 RAII and is the distinguishing point of C++. It "wraps" resources into objects. std::string
is a perfect example. This snippet:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
实际上分配了可变数量的内存.std::string
对象使用堆分配内存并在其析构函数中释放它.在这种情况下,您确实不需要需要手动管理任何资源,并且仍然可以获得动态内存分配的好处.
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 not need to manually manage any resources and still got the benefits of dynamic memory allocation.
特别是,它暗示在这个片段中:
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.
基本上,最后一段总结了它.尽可能频繁地使用自动存储使您的程序:
Basically, the last paragraph sums it up. Using automatic storage as often as possible makes your programs:
- 打字速度更快;
- 运行时更快;
- 不易发生内存/资源泄漏.
在引用的问题中,还有其他问题.特别是以下类:
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.
}
原因是 std::string
正确定义了复制构造函数.考虑以下程序:
The reason is that std::string
properly defines a copy constructor. Consider the following program:
int main ()
{
Line l1;
Line l2 = l1;
}
使用原始版本,该程序可能会崩溃,因为它对同一字符串使用了两次 delete
.使用修改后的版本,每个 Line
实例将拥有自己的字符串 instance,每个实例都有自己的内存,并且都将在程序结束时释放.
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.
由于上述所有原因,RAII 被认为是 C++ 中的最佳实践.但是,还有一个不是立即显而易见的额外好处.基本上,它比各个部分的总和要好.整个机制组成.它可以扩展.
Extensive use of RAII is 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.
如果您使用 Line
类作为构建块:
If you use the Line
class as a building block:
class Table
{
Line borders[4];
};
然后
int main ()
{
Table table;
}
分配四个 std::string
实例,四个 Line
实例,一个 Table
实例和所有字符串的内容,一切都是自动释放.
allocates four std::string
instances, four Line
instances, one Table
instance and all the string's contents and everything is freed automagically.
相关文章