为什么C ++程序员应该尽量减少对'新'的使用?

我偶然发现堆栈溢出问题使用std :: list <std :: string>时使用std :: string的内存泄漏,其中一个注释说:

停止使用new东西。 我看不出任何你在任何地方使用新的理由。 您可以在C ++中按值创建对象,这是使用该语言的巨大优势之一。 您不必分配堆中的所有内容。 不要像Java程序员那样思考。

我不确定他的意思。 为什么应该尽可能经常通过C ++的价值创建对象,它在内部会产生什么差异? 我误解了答案吗?


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

堆栈始终以顺序方式分配内存。 它可以这样做,因为它要求您以相反的顺序释放内存(先进先出:FILO)。 这是许多编程语言中局部变量的内存分配技术。 这是非常非常快的,因为它需要最少的簿记,下一个分配的地址是隐含的。

在C ++中,这被称为自动存储,因为存储在作用域结束时自动声明。 只要当前代码块(使用{}分隔)的执行完成,该块中所有变量的内存就会自动收集。 这也是调用析构函数来清理资源的时刻。

堆允许更灵活的内存分配模式。 记账更复杂,分配更慢。 由于没有隐式释放点,因此必须使用deletedelete[] (C中的free )手动释放内存。 但是,缺乏隐式释放点是堆的灵活性的关键。

使用动态分配的原因

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

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

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

  • 你想分配内存,它将在离开当前块后保留。 例如,你可能想写一个返回文件内容的函数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;
    }
    

    使用原始版本,该程序可能会崩溃,因为它使用相同字符串两次delete 。 使用修改后的版本,每个Line实例将拥有自己的字符串实例,每个实例都有自己的内存,并且两者都将在程序结束时释放。

    其他说明

    由于以上所有原因,广泛使用RAII被认为是C ++的最佳实践。 但是,还有一个不明显的额外好处。 基本上,它比各部分的总和要好。 整个机制组成。 它缩放。

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

     class Table
     {
          Line borders[4];
     };
    

    然后

     int main ()
     {
         Table table;
     }
    

    分配四个std::string实例,四个Line实例,一个Table实例和所有字符串的内容,所有内容都被自动释放。


    因为堆叠速度快而且万无一失

    在C ++中,只需一条指令即可为堆栈上的空间分配给定函数中的每个本地作用域对象,并且不可能泄漏任何内存。 该评论打算(或应该打算)说“使用堆栈而不是堆”。


    这很复杂。

    首先,C ++不是垃圾收集。 因此,对于每一个新的,都必须有相应的删除。 如果你没有把这个删除,那么你有一个内存泄漏。 现在,对于这样一个简单的例子:

    std::string *someString = new std::string(...);
    //Do stuff
    delete someString;
    

    这很简单。 但是如果“Do stuff”引发异常会发生什么? 糟糕:内存泄漏。 如果“做什么”问题会提前return会发生什么? 糟糕:内存泄漏。

    这是最简单的情况。 如果您恰好将该字符串返回给某人,现在他们必须将其删除。 如果他们把它作为一个论点,接受它的人是否需要删除它? 他们什么时候应该删除它?

    或者,你可以这样做:

    std::string someString(...);
    //Do stuff
    

    delete 。 该对象是在“堆栈”上创建的,一旦超出范围就会被销毁。 你甚至可以返回对象,从而将其内容传递给调用函数。 您可以将对象传递给函数(通常作为引用或常量引用: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis) 。等等。

    全部没有newdelete 。 毫无疑问谁拥有记忆或谁负责删除它。 如果你这样做:

    std::string someString(...);
    std::string otherString;
    otherString = someString;
    

    据了解, otherString具有someString数据的副本。 它不是一个指针; 它是一个单独的对象。 他们可能碰巧拥有相同的内容,但您可以更改其中一个而不会影响其他内容:

    someString += "More text.";
    if(otherString == someString) { /*Will never get here */ }
    

    看到这个想法?

    链接地址: http://www.djcxy.com/p/1059.html

    上一篇: Why should C++ programmers minimize use of 'new'?

    下一篇: What is the difference between a definition and a declaration?