为什么C ++程序员最小化使用“新”? [英] Why should C++ programmers minimize use of 'new'?

查看:134
本文介绍了为什么C ++程序员最小化使用“新”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我偶然发现Stack Overflow问题使用std :: list时,内存泄漏与std :: string< std :: string> 其中一条评论说:



< blockquote>

停止使用 new 这么多。我看不到任何原因你使用新的任何地方
你做了。你可以通过C ++中的值创建对象,它是
中使用语言的巨大优势之一。你不必在heap *上分配
的一切。停止像一个Java程序员一样思考。


*在引文中修正错误的堆栈 >

我不知道他的意思。为什么应该使用C ++中的值尽可能多地创建对象,它在内部有什么区别?

解决方案

有两种广泛使用的内存分配技术:自动分配和动态分配。通常,每个堆栈和堆都有一个相应的内存区域。



堆栈



堆栈总是以顺序方式分配内存。它可以这样做,因为它要求您以相反的顺序释放内存(先进先出,FILO)。这是用于许多编程语言中的局部变量的存储器分配技术。



在C ++中,这被称为自动存储因为存储在范围结束时自动声明。一旦执行当前代码块(使用 {} 定界)完成,则会自动收集该块中所有变量的内存。这也是调用清除资源的时刻。





堆允许更灵活的内存分配模式。记账更复杂,分配更慢。因为没有隐式释放点,您必须手动释放内存,使用 delete delete [] c $ c>免费)。



使用动态分配的原因



即使使用堆较慢,并且可能导致内存泄漏或内存碎片,动态分配也有很好的用例,因为它的限制较少。



使用动态分配的关键原因:




  • 您不知道编译时需要多少内存。例如,当将文本文件读入字符串时,通常不知道文件具有什么大小,因此您无法确定在运行程序之前要分配多少内存。


  • 您想要分配内存,在离开当前块后会持续存储。例如,你可能需要写一个返回文件内容的函数 string readfile(string path)。在这种情况下,即使堆栈可以保存整个文件内容,也不能从函数返回并保留分配的内存块。




为什么动态分配通常是不必要的



在C ++中有一个简洁的结构,称为 destructor 。此机制允许您通过将资源的生命周期与变量的生存期对齐来管理资源。此技术称为 RAII ,是C ++的区别点。它将资源包装到对象中。 std :: string 是一个完美的例子。此片段:

  int main(int argc,char * argv [])
{
std: :string program(argv [0]);
}

实际上分配了可变的内存量。 std :: string 对象使用堆分配内存,并在其析构函数中释放它。在这种情况下,您需要手动管理任何资源,仍然能够获得动态内存分配的好处。



在此代码片段中:

  int main(int argc,char * argv [])
{
std :: string * program = new std :: string(argv [0]); //坏了!
delete程序;
}

有不必要的动态内存分配。该程序需要更多的打字(!),并引入忘记释放内存的风险。



为什么你应该尽可能经常使用自动存储



基本上,最后一段总结起来。尽可能频繁使用自动存储功能可以让您的程序:




  • 更快地输入;




奖励积分



在引用的问题中,还有其他问题。特别是下面的类:

  class Line {
public:
Line
〜Line();
std :: string * mString;
};

Line :: Line(){
mString = new std :: string(foo_bar);
}

Line ::〜Line(){
delete mString;
}

实际上使用比下面更危险:

  class Line {
public:
Line();
std :: string mString;
};

Line :: Line(){
mString =foo_bar;
//注意:有一个更清洁的方式来写这个。
}

原因是 std :: string 正确定义了一个复制构造函数。考虑以下程序:

  int main()
{
Line l1;
Line l2 = l1;
}

使用原始版本,此程序可能会崩溃,因为它使用 delete 两次。使用修改后的版本,每个 Line 实例将拥有自己的字符串 instance ,每个都有自己的内存,

广泛使用 RAII 被认为是C ++的最佳实践,因为上述所有原因。然而,还有一个额外的好处,并不是立即显而易见的。基本上,它比它的部分的总和更好。整个机制组成



如果您使用 Line 类作为构建块:

  class Table 
{
线边框[4];
};

然后

  int main()
{
表table;
}

分配四个 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:

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.

*Fixed misspoken "stack" in the citation.

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?

解决方案

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.

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.

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.

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.

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

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.

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:

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.

Other notes

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.

If you use the Line class as a building block:

 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.

这篇关于为什么C ++程序员最小化使用“新”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆