我应该将 RAII 应用于我分配的所有数组吗?

Should I apply RAII to all arrays I allocated?

我现在正在学习C++。这是一种非常复杂的语言,我不确定应该使用哪个功能以及何时使用。

C++ Primer 介绍 RAII 作为确保异常安全的方法。这是否意味着,作为一种良好的行为,当我想使用数组时,我应该将数组放入 class 以分配和销毁资源。我知道我的想法很简单,或者天真。

我只是好奇什么是好的 C++ 编码行为。

RAII 表示资源与对象生命周期相关联。
C++ 的每个 STL class 都遵循这个原则,这意味着如果你需要一个数组,你可以简单地使用 std::vector.

向量的析构函数 class 将在您的实例超出范围时负责删除资源。

这意味着在你的情况下,而不是像这样使用新的:

int *array = new int[n];

你应该使用:

vector<int> array(n);

如果你真的需要在堆上分配一个共享指针,并且使用 RAII 仍然是安全的,你可以这样做(需要 C++11):

shared_ptr<vector<int>> array(new vector<int>(10));

这取决于您如何创建阵列。如果你在这样的代码块中创建它

int arr[5];

它有自动存储功能。该数组在超出范围时会自动销毁。

唯一需要显式管理对象生命周期的情况是动态分配它们,可能像这样:

int* arr = new int[5];

这分配了一个 5 ints 的数组,稍后需要像这样手动 deleted:

delete[] arr;

RAII 的想法是我们可以让 class 的构造函数执行动态分配,而其析构函数执行释放。那么如果我们创建这个class类型的对象,它们会在内部进行动态分配,但是我们可以确定内存在完成时会被正确释放。

所以是的,如果你真的想要一个动态分配的数组,你可以将它封装在你自己的 class 中。然而,这已经以更强大的形式存在:std::vectorstd::vector 类型在内部管理一个动态分配的数组,但也允许您在 运行 时间向其中添加元素和从中删除元素。

请注意,这不是特定于数组的内容。最佳实践是将所有动态分配(即使是简单的 new int)封装在 classes 中。标准库提供了许多 classes 来帮助你做到这一点(特别是智能指针)。

RAII 是推荐的资源管理惯用语 -- 资源可以是线程、内存...我经常遇到不了解 RAII 或如何使用它的长期开发人员,所以问也没什么丢人的。不幸的是,我认为您需要一些背景知识和术语才能理解它。在 C++ 中,您可以在两种内存中的一种中创建对象:堆栈和堆。

堆栈: 考虑这段代码:

class Foo
{
    // some implementation
}

void bar()
{
  Foo f;
  // do something with f.
  // maybe it throws an exception, maybe not.
}

这里 Foo 类型的对象 f 已经在堆栈上创建。当 运行ning 程序离开创建 f 的范围时(在我们的示例中,当它离开函数 bar() 时),f 的析构函数将被调用,无论它如何离开范围。它可能因为函数执行成功而离开范围,也可能因为抛出异常而离开范围。

堆分配 现在考虑以下内容:

class Foo
{
// same thing
}

void bar()
{
Foo* f = new f;
// whatever
delete f;
}

在本例中,我们在 上创建了对象 f。您可以看出我们这样做是因为我们调用了 new 运算符。每当使用 new 创建对象时,我们都必须调用 delete 以释放内存。这可能容易出错,因为人们忘记调用 delete,或者他们 return 指针并且不清楚指针应该从哪里删除,或者因为抛出异常。 RAII 是所有这些的解决方案,它应该是您首选的工具。

但这并不意味着您应该将数组放在 class 中(在某些情况下确实如此,但可能不是您所指的方式)。考虑以下因素:

void foo()
{
     // c-style array, but fixed size.
     int bar_stack[5];

     // c-style array, dynamically allocated!
     unsigned int array_size = get_array_size();
     int* bar_heap = new int[array_size]

     // c++ way:
     std::vector bar_vector(array_size);
 }

在第一个例子中我们有一个固定的c风格数组,它是在栈上分配的,所以没什么好担心的。在第二种情况下,它是动态分配的(编译器不知道大小,它在 运行 时间可用),所以我们需要删除,这意味着我们需要防止异常并确保有一个明确所有者。在第三种情况下,我们只使用一个矢量,它是一个标准容器

现在我提供了所有这些,因为我担心您不会理解简短的回答:是的,您通常应该使用 RAII 来管理您的资源,但您应该使用标准容器,而不是数组。为什么?因为 std 容器为您提供了 RAII——除非您创建一个指针容器,或者在堆上分配您的向量。

希望对您有所帮助。