C++ 在容器中的初始化与作为局部变量的初始化不同

C++ initialize differently in container vs as local variable

这个问题是在我尝试为 OpenGL 缓冲区对象编写 RAII 包装器 class 时出现的。 OpenGL 创建缓冲区对象的方式是调用 void glGenBuffers(GLsizei n​, GLuint* buffers​) which "returns n buffer object names in buffers​." Also, note that the object name 0 is special to OpenGL, being a default value "like a NULL pointer."

所以我的第一个想法是创建一个 class buffer_object 喜欢

class buffer_object {
public:
    // constructors, destructor
private:
    unsigned int const name;
};

我想要两种不同的初始化行为:

问题是我没有完全理解列表初始化、值初始化和默认初始化。

关于std::array的默认构造函数,cppreference.com说它使用聚合初始化,这是一种列表初始化,它的值初始化数组中没有在数组中给出的元素初始化列表。关于值初始化,cppreference.com says,

if T is a class type with at least one user-provided constructor of any kind, the default constructor is called

那么,这是否意味着无法执行以下操作?

{
    buffer_object obj; // has some name given by glGenBuffers; ready to use

    int const n = 3;
    std::array<buffer_object, n> foo; // each element has name 0
    glGenBuffers(n, foo.data()); // gives each element its own name
}
// everything destructed using buffer_object::~buffer_object

— 因为我要求默认构造函数提供两种不同的行为?

也许我采取了错误的方法。

C++ 对象初始化不是,也不能基于 创建对象的位置 (除了普通类型的 default-initializing 对象,zero-initialize 用于全局变量,但不用于自动变量)。

如果你给一个类型一个non-trivial默认构造函数,你就是在做一个强有力的声明,即这种类型的对象需要在它们可以之前执行一些真正的代码被认为是这种类型的有效对象。因此,C++ 将假设您认真对待此声明,并确保在创建该类型的对象时在所有情况下都执行所述“真实代码”。

如果你给 buffer_object 一个生成缓冲区对象的默认构造函数,你就是说这种类型的对象应该始终拥有一个有效的 OpenGL 缓冲区对象(除非它们是 moved-from) .如果你给 buffer_object 一个默认构造函数来清零内部缓冲区对象句柄,你是说没有 OpenGL 缓冲区对象句柄是 buffer_object 处于的正常有效状态。

这两个语句没有half-way。默认为空或不是。

现在,您可以拥有备用构造函数。默认构造函数可以使句柄为零,而另一个构造函数采用简单的标记类型,明确表示它将生成一个句柄。或者更好的是,一个工厂函数如何使 buffer_objects 已生成句柄:

buffer_object gen_buffer()
{
  GLuint handle = 0;
  glGenBuffers(1, &handle);
  return buffer_object(handle);
};

auto obj = gen_buffer(); //Clearly states what is going on.

现在毫无疑问 obj 中的内容:它有一个生成的缓冲区对象句柄。因为这就是代码所说的内容。