使用在C ++中动态分配的数组有什么问题?

这个问题在这里已经有了答案:

  • 为什么C ++程序员应该尽量减少对'新'的使用? 17个答案
  • 理解术语和概念的含义 - RAII(资源获取初始化)11个答案

  • 我发现你的代码有三个主要问题:

  • 使用裸体,拥有指针。

  • 使用裸new

  • 使用动态数组。

  • 由于各自的原因,每个都是不合需要的。 我会试着依次解释每一个。

    (1)违反了我喜欢称之为子表达式的正确性,并且(2)违反了语句明智的正确性。 这里的想法是,没有声明,甚至没有任何子表达式,本身应该是一个错误。 我把“错误”这个术语松散地表示为“可能是一个错误”。

    编写好代码的想法是,如果出错,那不是你的错。 你的基本心态应该是一个偏执的懦夫。 根本不写代码是实现这一目标的一种方式,但由于它很少符合要求,所以最好的办法是确保无论你做什么,它都不是你的错。 你可以系统地证明这不是你的错误的唯一方法是,如果你的代码中没有任何一部分是错误的根源。 现在让我们再看看代码:

  • new std::string[25]是一个错误,因为它创建了一个泄漏的动态分配对象。 如果其他人在其他地方并且在任何情况下都记得清理,此代码只能有条件地成为非错误。

    首先,这需要将该表达式的值存储在某个地方。 这发生在你的案例中,但在更复杂的表达式中,可能很难证明它会在所有情况下发生(未指定的评估顺序,我在看着你)。

  • foo = new std::string[125]; 是一个错误,因为再次foo泄漏资源,除非星星对齐,并且某人在任何情况下并在正确的时间记住清理。

  • 到目前为止,编写此代码的正确方法是:

    std::unique_ptr<std::string[]> foo(std::make_unique<std::string[]>(25));
    

    请注意,此语句中的每个子表达式都不是程序错误的根本原因。 不是你的错。

    最后,就(3)而言,动态数组在C ++中是一种错误特性,应该基本上不会被使用。 有几个标准缺陷只与动态数组有关(并且不被认为是值得修复的)。 简单的说法是你不能在不知道大小的情况下使用数组。 您可能会说可以使用标记或逻辑删除值来动态标记数组的末尾,但这会使程序的正确性取决于数值,而不是依赖于类型,因此不能进行静态检查(“不安全”的定义“)。 你不能断言这不是你的错。

    所以你最终不得不为数组大小维护一个单独的存储空间。 并猜测你的实现必须复制这些知识,以便在你说delete[]时调用析构函数,这样就浪费了重复。 相反,正确的方法不是使用动态数组,而是使用基于元素的对象构造来分离内存分配(并通过分配器为它定制)。 将所有这些(分配器,存储,元素数)包装成一个简单的类是C ++的方式。

    因此你的代码的最终版本是这样的:

    std::vector<std::string> foo(25);
    

    您提出的代码不是异常安全的,也可以选择:

    std::vector<std::string> foo( 125 );
    //  no delete necessary
    

    是。 当然, vector稍后会知道大小,并且可以在调试模式下进行边界检查; 它可以通过(通过引用或者甚至通过值)传递给一个函数,然后它将能够使用它,而不需要任何额外的参数。 Array new遵循数组的C约定,并且C中的数组严重受损。

    据我所知,从来没有一个新的数组适合的情况。


    我听说在某些情况下这种使用(不是精确的代码,而是整体的动态分配)可能是不安全的,只能在RAII中使用。 为什么?

    以这个例子(类似于你的)为例:

    int f()
    {
        char *local_buffer = new char[125];
        get_network_data(local_buffer);
        int x = make_computation(local_buffer);
        delete [] local_buffer;
        return x;
    }
    

    这是微不足道的。

    即使你正确地编写了上面的代码,有些人可能会在一年之后,并在你的函数中添加一个条件的,或者十个或二十个:

    int f()
    {
        char *local_buffer = new char[125];
        get_network_data(local_buffer);
        int x = make_computation(local_buffer);
        if(x == 25)
        {
            delete[] local_buffer;   
            return 2;
        }
        if(x < 0)
        {
            delete[] local_buffer; // oops: duplicated code
            return -x;
        }
        if(x || 4)
        {
            return x/4; // oops: developer forgot to add the delete line
        }
        delete[] local_buffer; // triplicated code
        return x;
    }
    

    现在,确保代码没有内存泄漏更加复杂:你有多个代码路径,每个代码都必须重复删除语句(并且我特意介绍了内存泄漏,以举例说明)。

    这仍然是一个微不足道的情况,只有一个资源(local_buffer),它(天真地)假定代码在分配和释放之间不会引发任何异常。 这个问题导致不可维护的代码,当你的函数分配~10个本地资源时,可能抛出并且有多个返回路径。

    更重要的是,上述进展(简单,平凡的情况扩展到具有多个退出路径的更复杂功能,扩展到多个资源等等)是大多数项目开发过程中代码的自然发展过程。 不使用RAII,为开发人员更新代码创造了一种自然的方式,这种方式会降低项目的整个生命周期的质量( 这被称为cruft,是一件非常糟糕的事情 )。

    TLDR:在C ++中使用原始指针进行内存管理是一种不好的做法(尽管对于实现观察者角色,使用原始指针的实现很好)。 原料管理者的资源管理违反SRP和DRY原则)。

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

    上一篇: What is wrong with using arrays dynamically allocated in C++?

    下一篇: Confused about declaration, definition