发布日期:2018-03-26
为什么C++程序员要尽量减少使用new?+ 查看更多
为什么C++程序员要尽量减少使用new?
+ 查看更多
发布日期:2018-02-26 17:12
分类:CPlusPlus
浏览次数:504
我在浏览Stack Overflow的时候偶然发现这样一个问题,这是问题的地址http://stackoverflow.com/q/3428750/211563。
其中有一个回答是这样的:
尽量减少new的使用吧,没有任何理由促使你去这么做的。在C++中你可以通过赋值来创建对象,这是C++语言的巨大优势之一,你不必分配堆上的所有内容。请不要再像一个Java程序员那样去思考问题了。
其中有一个回答是这样的:
尽量减少new的使用吧,没有任何理由促使你去这么做的。在C++中你可以通过赋值来创建对象,这是C++语言的巨大优势之一,你不必分配堆上的所有内容。请不要再像一个Java程序员那样去思考问题了。
我真的不知道他是什么意思。为什么要尽可能多地去使用C++中的赋值来创建对象,它们在内部有什么区别吗?还是我误解了他的回答?
回答
有两种广泛使用的内存分配技术:自动分配和动态分配。通常,他们分别对应内存区域:栈和堆。
栈
栈始终以顺序方式分配内存。 它可以这样做,因为它要求你以相反的顺序释放内存(先进先出,FILO)。许多编程语言都采用这样的局部变量的存储分配技术。 它非常非常快,因为它需要很少的bookkeeping,并且分配的下一个地址是隐式的。
在C++中,这称为自动存储,因为存储被自动声明。 一旦当前代码块(使用{}分隔)执行完毕之后,该块中所有变量的内存将自动回收。这也是调用析构函数来清理资源的时刻。
堆
堆允许更灵活的内存分配模式。bookkeeping更复杂,分配更慢。因为没有隐式释放点,所以必须手动释放内存,使用delete或delete []。然而,缺少隐式释放点是堆灵活性的关键。
使用动态分配的原因
即使使用堆较慢,并且可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较少。
使用动态分配的两个主要原因:
·你不知道在编译时需要多少内存。 例如,当将文本文件读入字符串时,我们通常不知道文件的大小,因此在运行程序之前无法确定要分配多少内存。
·你想要分配内存,就算离开当前代码块后依然保持该内存不释放。 例如,你可能想编写一个返回文件内容的函数string readfile(string path)。 在这种情况下,即使栈可以存储整个文件内容,你也不能把它作为函数的返回值,因为离开这个代码段之后,该内存会被释放。
·你想要分配内存,就算离开当前代码块后依然保持该内存不释放。 例如,你可能想编写一个返回文件内容的函数string readfile(string path)。 在这种情况下,即使栈可以存储整个文件内容,你也不能把它作为函数的返回值,因为离开这个代码段之后,该内存会被释放。
为什么动态分配通常是不必要的
在C++中有一个干净利落的函数,称为析构函数。这种机制允许你通过将资源的生命周期与变量的生存期对齐来管理资源。 这种技术称为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]); // Bad! delete program; }
就是不必要的动态内存分配。 该程序增加了代码量,并增加了忘记释放已分配内存的风险,没有明显的好处。
为什么你应该尽可能多地使用自动存储?
总结起来,尽可能多地使用自动存储功能使你的程序:
·更快的类型;
·运行时更快;
·不易出现内存/资源泄漏。
·更快的类型;
·运行时更快;
·不易出现内存/资源泄漏。
额外补充
在提到的问题中,还有其他的问题,特别是下面这个类
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"; // note: there is a cleaner way to write this. }原因是std::string定义了一个拷贝函数。 考虑下面的程序:
int main () { Line l1; Line l2 = l1; }
使用原始版本,这个程序可能会崩溃,因为它使用相同的字符串,却删除了两次。使用修改版本,每个Line实例将拥有自己的字符串实例,每个实例都有自己的内存,并且都将在程序结束时释放。
其他说明
由于上述所有原因,广泛使用RAII是C++的最佳实践。另外,还有一个并不显而易见的好处。
如果你使用Line类作为一个构造块:
class Table { Line borders[4]; };然后
int main () { Table table; }分配的四个std :: string实例,四个Line实例,一个Table实例和所有字符串的内容,一切都自动释放。