【问题标题】:Memory Allocation and Try-Catch Block内存分配和 Try-Catch 块
【发布时间】:2012-12-22 21:45:24
【问题描述】:

我有一个函数来分配一个二维数组,以免占用比我需要的更多的内存:

_>

template <class Xvar> Xvar** New2 (unsigned int rows,unsigned int cols)
{
    Xvar**  mem;
    unsigned int size, i;

    size = rows * cols;
    mem = new Xvar* [rows];
    mem [0] = new Xvar [size];
    for (i=1;i<rows;i++)
        mem [i] = &mem [0][i*cols];
    return mem;
}

现在,我需要检查该内存是否已分配。 (处理内存分配错误), 不会降低功能的性能。

我应该为每个内存分配使用一个 try-catch 块,还是只为函数使用一个唯一的 try-catch 块。

template <class Xvar> Xvar** New2 (unsigned int rows,unsigned int cols)
{
    Xvar**  mem;
    unsigned int size, i;

    size = rows * cols;
    try {
    mem = new Xvar* [rows];
    }
    catch (...) { assert (...) } 
    try {
    mem [0] = new Xvar [size];
    } catch (...) { assert (...) }
    for (i=1;i<rows;i++)
        mem [i] = &mem [0][i*cols];
    return mem;
}

或类似的东西:

template <class Xvar> Xvar** New2 (unsigned int rows,unsigned int cols)
{
    try { 
    Xvar**  mem;
    unsigned int size, i;

    size = rows * cols;
    mem = new Xvar* [rows];
    mem [0] = new Xvar [size];
    for (i=1;i<rows;i++)
        mem [i] = &mem [0][i*cols];
    return mem;
     }catch  (...) { assert (...) }
}

我认为,不推荐第二种方式,因为如果第一种 new 失败,mem 为 NULL, 因此,如果我们执行 mem [0] ,我们正在访问未分配的内存,因此应用程序此时会失败,并且无法捕获错误。

【问题讨论】:

  • 1) 如果new 失败,mem isn't anything because it's out of scope, because new` 会在失败时抛出异常(我想你知道这是因为你试图捕捉它)。 2)如果你不能对它做任何事情,那么捕捉异常有什么意义? 3) 使用std::vector&lt;&gt; 4) 在你的代码工作之前停止担心性能。给你:int main(){} 世界上最快的托管 C++ 程序!带你去任何地方?
  • new失败后你打算做什么?
  • @Victor Ruiz:当您说“不推荐第二种方式”时,您是指“基于try-catch的第二种解决方案”,还是“问题中的第二个代码示例”(您的第一个解决方案是基于try-catch)?
  • 现在,我明白了,我很傻。但是如果我想知道什么新的失败,第一个或第二个,我应该使用第一个解决方案还是第二个?

标签: c++ memory-management


【解决方案1】:

在第二种方式中,如果第一个 new 失败,则评估立即跳转到 catch 块,甚至从不尝试访问 mem[0]

在任何情况下,如果您想让分配失败并轻松检测到这一点,您可能应该使用nothrow 变体,如果分配失败,它只会返回NULL。所以像

mem = new (nothrow) Xvar*[rows];
if (!mem) {
    // allocation failed, do whatever you want
}
mem[0] = new (nothrow) Xvar[size];
if (!mem[0]) {
    // allocation failed, do whatever you want
}

【讨论】:

  • 我应该使用“nothrow”还是“no throw”
  • @VictorRuiz:使用(nothrow)(no throw) 是我已经纠正的错字。
【解决方案2】:

根本不捕获异常,只需在函数展开时使用 RAII 清理内存:

template <class Xvar> Xvar** New2 (unsigned int rows,unsigned int cols)
{
    unsigned int size = rows * cols;
    std::unique_ptr<Xvar*[]> mem(new Xvar* [rows]);
    mem[0] = new Xvar [size];
    for (i=1; i<rows; i++)
        mem[i] = &mem[0][i*cols];
    return mem.release();
}
  • 如果第一个 new 失败,则您的函数没有做任何事情并且没有泄漏任何内存:std::bad_alloc 被抛出,仅此而已
  • 如果第二个失败,unique_ptr 析构函数会处理该内存,所以你再次没事。
  • 接下来的三行不能抛出,所以不会导致第二个分配泄漏

正如 GmanNickG 指出的那样,这仍然是糟糕的代码。这是您要求的糟糕代码,但我不想给人留下我认可原始设计的印象。我不。太可怕了。

一旦调用者成功获取了他们的Xvar**,处理它的唯一方法是:

int **ii = New2<int>(x, y);
...
delete [] ii[0];
delete [] ii;

这是脆弱和令人不快的。两个更好的设计是:

  1. 使用真正的矩阵/二维数组模板类来管理存储,并且可以按值返回。然后,当这个值对象超出范围时,内存将被回收

  2. 使用带有自定义删除器的智能指针

    template <typename T> struct 2dDelete
    {
        void operator() (T **rows) {
            delete [] rows[0];
            delete [] rows;
        }
    };
    
    template <typename T>
    std::unique_ptr<T*[], 2dDelete<T>> 2dNew (unsigned rows, unsigned cols)
    {
        unsigned size = rows * cols;
        std::unique_ptr<Xvar*[]> tmp(new Xvar* [rows]);
        tmp[0] = new Xvar [size];
        for (i=1; i<rows; i++)
            tmp[i] = &tmp[0][i*cols];
        // hand-over from the intermediate pointer (which only owned the
        // top-level row allocation) to the caller's pointer (which will
        // own, and clean up, the whole thing).
        return std::unique_ptr<T*[], 2dDelete<T>> arr(mem.release());
    }
    

【讨论】:

  • 当然,应该在任何地方都应用这个,包括内部分配和返回类型。
  • 返回类型,无论如何(我只是坚持使用 OP 的原型)。如果内部分配抛出,它从未被分配,所以这不会泄漏。
  • 如果没有人打电话给delete就可以了。最好把它包起来,不要担心。
  • 如果new抛出bad_alloc,有什么要删除的?相反,如果 Xvar 默认构造函数抛出,编译器仍然负责清理它。
  • 我说的是这条线mem[0] = new Xvar [size];。假设您取出mem.release() 行;什么删除了mem[0]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-25
  • 2017-09-30
  • 2016-05-21
  • 1970-01-01
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
相关资源
最近更新 更多