为什么在执行复制之前调用析构函数?

时间:2016-10-06 19:39:54

标签: c++

我有以下代码

class Mesh
{
public:
    Mesh();
    Mesh(std::vector<Vertex> vertices, std::vector<GLuint> indices);
    ~Mesh();
    void draw(Shader& shader);

private:
    std::vector<Vertex> mVertices;
    std::vector<GLuint> mIndices;
    GLuint mVBO;
    GLuint mEBO;
};

Mesh::Mesh(std::vector<Vertex> vertices, std::vector<GLuint> indices)
{   
    mIndices = indices;
    glGenBuffers(1, &mEBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndices.size() * sizeof(GLuint), &mIndices[0], GL_STATIC_DRAW);

    mVertices = vertices;
    glGenBuffers(1, &mVBO);
    glBindBuffer(GL_ARRAY_BUFFER, mVBO);
    glBufferData(GL_ARRAY_BUFFER, mVertices.size() * sizeof(Vertex), &mVertices[0], GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    computeBoundingBox();
}

Mesh::~Mesh()
{
    glDeleteBuffers(1, &mVBO);
    glDeleteBuffers(1, &mEBO);
}

void Mesh::draw(Shader& shader)
{   
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
    glBindBuffer(GL_ARRAY_BUFFER, mVBO);

    GLuint vpos = glGetAttribLocation(shader.program(), "vPosition");
    GLuint vnor = glGetAttribLocation(shader.program(), "vNormal");

    glVertexAttribPointer(vpos, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(vnor, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)sizeof(Vector));

    shader.bind();
    glEnableVertexAttribArray(vpos);
    glEnableVertexAttribArray(vnor);
    glDrawElements(GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, 0);
    shader.unbind();

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void loadSquare(Mesh& mesh)
{
    std::vector<Vertex> vertices;
    vertices.push_back(Vertex(Vector(0.5f, 0.5f, 0.f), Vector(1.f, 0.f, 0.f)));
    vertices.push_back(Vertex(Vector(-0.5f, 0.5f, 0.f), Vector(0.f, 1.f, 0.f)));
    vertices.push_back(Vertex(Vector(-0.5f, -0.5f, 0.f), Vector(0.f, 0.f, 1.f)));
    vertices.push_back(Vertex(Vector(0.5f, -0.5f, 0.f), Vector(1.f, 0.f, 1.f)));

    std::vector<GLuint> indices;
    indices.push_back(0);
    indices.push_back(1);
    indices.push_back(2);
    indices.push_back(0);
    indices.push_back(2);
    indices.push_back(3);

    mesh = Mesh(vertices, indices);
}

int main(int argc, char** argv)
{
    // Create opengl context and window
    initOGL();

    // Create shaders
    Shader shader("render.vglsl", "render.fglsl");

    Mesh mesh;
    loadSquare(mesh);

    while (!glfwWindowShouldClose(window))
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        mesh.draw(shader);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

如果我尝试运行它,它只会在它创建的窗口上显示灰色图像。

使用调试器跟踪应用程序后,当它到达行mesh = Mesh(vertices, indices)时,它会为OpenGL创建缓冲区并将顶点和索引std :: vectors复制到作为参数传递的变量mesh 。 但是,它还调用由Mesh(vertices,indices)创建的对象的析构函数,这反过来使OpenGL上下文中的缓冲区无效,因此当应用程序到达mesh.draw(shader)时,缓冲区网格点将不再有效。

move-constructor是否可以帮助我解决我的问题,即避免调用Mesh(vertices,indices)的析构函数?还有其他解决方案吗?

3 个答案:

答案 0 :(得分:4)

从您的来源,您正在绑定到立即销毁的矢量。

基本上,在loadSquare函数中,当你在loadSquare的最后一行中的赋值右侧写 Mesh(vertices,indices); 时,你正在创建一个网格并绑定它。

mesh = Mesh(vertices, indices);

认为这样的一行:

  ...
  Mesh m1(vertices, indices);  // a 
  mesh= m1;                    // b
  // m1 gets destroyed here.
}

线(a)创建并绑定网格。

当你将它分配给第b行的网格时,mesh.mVertices和mesh.mIndices将获得向量的副本,mVBO和mEBO将获得绑定值的副本。

将该行(b)视为写作

mesh.mVertices= m1.mVertices;  // mesh gets a new vector with the same values
mesh.mIndices= m1.mIndices;    // mesh gets a new vector with the same values
mesh.mVBO= m1.mVBO;
mesh.mEBO= m1.mEBO;

在loadSquare()结束时,m1将被销毁(析构函数被调用)。

在你的调用函数中,你将得到包含mVBO和mEBO成员的网格,这些成员绑定到被销毁的向量。它包含自己的向量,它们具有相同的值,但它们是从不绑定的不同内存位置的副本。

有多种方法可以解决这个问题,例如通过指针返回方形网格。或编写一个赋值运算符(google for shallow copy)。

但我的建议是创建一个空构造函数和一个额外的fillMesh函数,就像你当前的cotstructor一样。

Mesh::Mesh(void);            // set mVBO and mEBO to zero.
void Mesh::fillMesh(std::vector<Vertex> vertices, std::vector<GLuint> indices);  // same code as your current constructor.

然后重写你的loadSquare函数:

void loadSquare(Mesh& mesh)
{
 std::vector<Vertex> vertices;
 vertices.push_back(Vertex(Vector(0.5f, 0.5f, 0.f), Vector(1.f, 0.f, 0.f)));
 vertices.push_back(Vertex(Vector(-0.5f, 0.5f, 0.f), Vector(0.f, 1.f, 0.f)));
 vertices.push_back(Vertex(Vector(-0.5f, -0.5f, 0.f), Vector(0.f, 0.f, 1.f)));
 vertices.push_back(Vertex(Vector(0.5f, -0.5f, 0.f), Vector(1.f, 0.f, 1.f)));

 std::vector<GLuint> indices;
 indices.push_back(0);
 indices.push_back(1);
 indices.push_back(2);
 indices.push_back(0);
 indices.push_back(2);
 indices.push_back(3);

 mesh.fillMesh(vertices, indices);
}

因此,loadSquare将创建顶点和索引,并将它们从调用函数设置到网格中并绑定它们。

附加说明(清洁解决方案):

  • 析构函数应该也可以取消绑定向量和索引 GL。
  • fillMesh函数应该检查网格是否是 在设置和绑定之前已经绑定并取消绑定旧向量 新的(如果你在活动网格上再次调用fillMesh)。
  • 您可能仍应编写一个调用fillMesh的赋值运算符: Mesh :: operator =(const Mesh&amp; other); //谷歌浅拷贝

答案 1 :(得分:1)

当您实例化Mesh(vestices,indices)时,将调用Mesh构造函数。这只是一个临时对象实例。然后代码调用赋值运算符将临时对象实例复制到网格变量中。由于您还没有定义operator = [给定提供的代码],它会执行默认的赋值行为。完成该赋值后,将调用~Mesh析构函数。

答案 2 :(得分:1)

你违反了rule of three (rule of five),当然你在完成任务后会遇到问题。在这一行:

mesh = Mesh(vertices, indices);

您创建一个在语句后立即销毁的临时对象。因此,正确实施或禁止复制ctor和复制赋值运算符来解决问题。您可能还希望实现move ctor和移动赋值运算符,尤其是在禁止复制时。