C++ RAII 类中的 OpenGL 对象不再有效

2021-12-12 00:00:00 opengl c++ c++11

我在 C++ 类中有一个 OpenGL 对象.因为我正在使用 RAII,所以我想让析构函数删除它.所以我的课程看起来像:

I have an OpenGL object in a C++ class. Since I'm employing RAII, I want to have the destructor delete it. So my class looks something like:

class BufferObject
{
private:
  GLuint buff_;

public:
  BufferObject()
  {
    glGenBuffers(1, &buff_);
  }

  ~BufferObject()
  {
    glDeleteBuffers(1, &buff_);
  }

//Other members.
};

这似乎有效.但是每当我执行以下任何操作时,我都会在使用它时遇到各种 OpenGL 错误:

This seems like it works. But any time I do any of the following, I start getting various OpenGL errors when I use it:

vector<BufferObject> bufVec;
{
  BufferObject some_buffer;
  //Initialize some_buffer;
  bufVec.push_back(some_buffer);
}
bufVec.back(); //buffer doesn't work.

BufferObject InitBuffer()
{
  BufferObject buff;
  //Do stuff with `buff`
  return buff;
}

auto buff = InitBuffer(); //Returned buffer doesn't work.

怎么回事?

注意:这是为这些问题建立规范答案的尝试.

Note: this is an attempt to build a canonical answer to these questions.

推荐答案

所有这些操作都复制 C++ 对象.由于您的类没有定义复制构造函数,因此您将获得编译器生成的复制构造函数.这只是复制对象的所有成员.

All of those operations copy the C++ object. Since your class did not define a copy constructor, you get the compiler-generated copy constructor. This simply copies all of the members of the object.

考虑第一个例子:

vector<BufferObject> bufVec;
{
  BufferObject some_buffer;
  //Initialize some_buffer;
  bufVec.push_back(some_buffer);
}
bufVec.back(); //buffer doesn't work.

当您调用push_back 时,它会将some_buffer 复制到vector 中的BufferObject 中.因此,在我们退出该作用域之前,有两个 BufferObject 对象.

When you call push_back, it copies some_buffer into a BufferObject in the vector. So, right before we exit that scope, there are two BufferObject objects.

但是它们存储什么 OpenGL 缓冲区对象?好吧,它们存储相同的.毕竟,对于 C++,我们只是复制了一个整数.所以两个 C++ 对象都存储相同的整数值.

But what OpenGL buffer object do they store? Well, they store the same one. After all, to C++, we just copied an integer. So both C++ objects store the same integer value.

当我们退出该作用域时,some_buffer 将被销毁.因此,它将在此 OpenGL 对象上调用 glDeleteBuffers.但是向量中的对象仍然有自己的那个 OpenGL 对象名称的副本.哪个已经被摧毁.

When we exit that scope, some_buffer will be destroyed. Therefore, it will call glDeleteBuffers on this OpenGL object. But the object in the vector will still have its own copy of that OpenGL object name. Which has been destroyed.

所以你不能再使用它了;因此出现错误.

So you can't use it anymore; hence the errors.

同样的事情发生在你的 InitBuffer 函数上.buff 复制到返回值后会被销毁,导致返回的对象一文不值.

The same thing happens with your InitBuffer function. buff will be destroyed after it is copied into the return value, which makes the returned object worthless.

这都是因为违反了C++中所谓的3/5法则".您创建了一个析构函数,而没有创建复制/移动构造函数/赋值运算符.这很糟糕.

This is all due to a violation of the so-called "Rule of 3/5" in C++. You created a destructor without creating copy/move constructors/assignment operators. That's bad.

要解决这个问题,您的 OpenGL 对象包装器应该是仅移动类型.您应该删除复制构造函数和复制赋值运算符,并提供将移动对象设置为对象 0 的移动等效项:

To solve this, your OpenGL object wrappers should be move-only types. You should delete the copy constructor and copy assignment operator, and provide move equivalents that set the moved-from object to object 0:

class BufferObject
{
private:
  GLuint buff_;

public:
  BufferObject()
  {
    glGenBuffers(1, &buff_);
  }

  BufferObject(const BufferObject &) = delete;
  BufferObject &operator=(const BufferObject &) = delete;

  BufferObject(BufferObject &&other) : buff_(other.buff_)
  {
    other.buff_ = 0;
  }

  BufferObject &operator=(BufferObject &&other)
  {
    //ALWAYS check for self-assignment
    if(this != &other)
    {
      Release();
      buff_ = other.buff_;
      other.buff_ = 0;
    }

    return *this;
  }

  ~BufferObject() {Release();}

  void Release();
  {
    if(buff_)
      glDeleteBuffers(1, &buff_);
  }

//Other members.
};

有各种其他技术用于为 OpenGL 对象制作仅移动的 RAII 包装器.

There are various other techniques for making move-only RAII wrappers for OpenGL objects.

相关文章