std::vector::emplace_back bug when returning references (C++17)

旧城冷巷雨未停 提交于 2021-01-27 15:31:01

问题


I've been trying to trace a bug for 10+ hours now, and by now I'm starting to think that the bug can't be on my side. However, I have a feeling it could be me who's just forgetting or misunderstanding something.

I have a class member of type std::vector called temp_materials, and inside the constructor (when temp_materials is still empty), this code runs:

    Material &stonewallbug = temp_materials.emplace_back(resource_lib.get_shader("DeferredGeometryShader"));
    stonewallbug.set_texture("texture_diffuse1", resource_lib.get_texture("StonewallDiffuse"));
    stonewallbug.set_texture("texture_specular1", resource_lib.get_texture("StonewallSpecular"));

    Material &containerbug = temp_materials.emplace_back(resource_lib.get_shader("DeferredGeometryShader"));
    containerbug.set_texture("texture_diffuse1", resource_lib.get_texture("ContainerDiffuse"));
    containerbug.set_texture("texture_specular1", resource_lib.get_texture("ContainerSpecular"));

    Material stonewall1 = temp_materials[0];
    Material container1 = temp_materials[1];

    Material stonewall2 = stonewallbug;
    Material container2 = containerbug;

If nothing goes wrong during the copying, stonewall1's contents should be equal to stonewall2, as should container1 to container2, correct?

However, I then insert all of them into a simple vector which's contents will be rendered later on:

    // Using temp_materials[0]
    auto stonewall1node = SceneNode(stonewall1, resource_lib.get_mesh("Cube"));
    stonewall1node.set_transform(glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)));
    m_scene_list.push_back(stonewall1node);

    // Using temp_materials[1]
    auto container1node = SceneNode(container1, resource_lib.get_mesh("Cube"));
    container1node.set_transform(glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 1.0f)));
    m_scene_list.push_back(container1node);

    // Using stonewallbug
    auto stonewall2node = SceneNode(stonewall2, resource_lib.get_mesh("Cube"));
    stonewall2node.set_transform(glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, 0.0f)));
    m_scene_list.push_back(stonewall2node);

    // Using containerbug
    auto container2node = SceneNode(container2, resource_lib.get_mesh("Cube"));
    container2node.set_transform(glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, 1.0f)));
    m_scene_list.push_back(container2node);

Now, I'd expect there to be 2 "container" cubes stacked on each other, and 2 "stonewall" cubes stacked on each other, but this is the result I'm getting:

If I however move the line Material stonewall2 = stonewallbug; to between the creation of stonewallbug and containerbug, I get the result I expect.

Noticing this, I made a very, very simple C++ program:

#include <iostream>
#include <vector>
#include <string>


int main() {
    std::vector<std::string> strings;

    std::string& ref1 = strings.emplace_back("1");
    std::string& ref2 = strings.emplace_back("2");

    std::cout << "ref1: " << ref1 << std::endl;
    std::cout << "ref2: " << ref2 << std::endl;

    std::cout << "strings[0]: " << strings[0] << std::endl;
    std::cout << "strings[1]: " << strings[1] << std::endl;


    return 0;
}

And the result when running this is a lot of nonsense characters. However, if I output ref1 before ref2 is emplaced, it outputs the expected results. On here it says that vector's emplace_back is supposed to return a reference to the inserted element, but to me it doesn't seem like it's working like it should.

Am I misunderstanding something, or is this just a very obvious bug in g++ 7.1?

Edit: I can't believe I spent so many hours on such an obvious thing... :)


回答1:


You have to be careful with references and iterators to container elements. Various mutating container operations invalidate references and iterators. The details vary by container and operation, but for std::vector, all inserting operations (such as push_back and emplace_back), as well as erasure from anything other than the end, invalidate both references and iterators in general (but see below for ways to avoid this).

Therefore, in the following code,

std::vector<T> v;

T & a = v.emplace_back(a, b, c);
T & b = v.emplace_back(x, y, z);

the second call of emplace_back invalidates the reference a. (And accessing an object through an invalid reference has undefined behaviour.)

To avoid invalidation in a std::vector, use the reserve and capacity facilities:

std::vector<T> v;
v.reserve(2);
T & a = v.emplace_back(a, b, c);
T & b = v.emplace_back(x, y, z);

An insertion at the end does not invalidate if the new size is less than or equal to the current capacity.



来源:https://stackoverflow.com/questions/47253016/stdvectoremplace-back-bug-when-returning-references-c17

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!