c++referenceundefined-behavior

Weird behavior of class using const. references


I wanted to have a class variable that has public readonly access and can be edited in a private context. In this previous Stackoverflow Question it was described to use const references. However for me this leads to strange behaviour.

    class Vec3 {
    public:
        double x, y, z;
        Vec3(double x, double y, double z) : x(x), y(y), z(z) {}
        Vec3() : x(0), y(0), z(0) {}
    };
    
    std::ostream& operator<<(std::ostream& os, const Vec3& a) {
        os << "(" << a.x << ", " << a.y << ", " << a.z << ")";
        return os;
    }
    
    class Triangle3 {
    private:
        Vec3 _a, _b, _c;
    public:
        Triangle3(const Vec3& a, const Vec3& b, const Vec3& c) : _a(a), _b(b), _c(c) {}
        Triangle3() : _a(Vec3()), _b(Vec3()), _c(Vec3()) {}
    
        const Vec3& a = _a;
        const Vec3& b = _b;
        const Vec3& c = _c;
    };
    
    std::ostream& operator<<(std::ostream& os, const Triangle3& a) {
        os << "(" << a.a.x << ", " << a.a.y << ", " << a.a.z << "), ";
        os << "(" << a.b.x << ", " << a.b.y << ", " << a.b.z << "), ";
        os << "(" << a.c.x << ", " << a.c.y << ", " << a.c.z << ")";
        return os;
    }

    class Mesh {
    public:
        std::vector<Triangle3> triangles;
        Mesh(std::vector<Triangle3> triangles) : triangles(triangles) {}
        Mesh() {}
    };

    int main (int argc, char** argv) {
        std::vector<Triangle3> triangles;
        int N = 10;
        for (int i = 0; i < N; i++) {
                triangles.push_back(Triangle3(Vec3(i, i, i), Vec3(i, i, i), Vec3(i, i, i)));
        }
        std::cout << "Triangle 0: " << triangles[0] << std::endl;
        Mesh mesh(triangles);
        std::cout << "Triangle 0: " << mesh.triangles[0] << std::endl;
    }

This program outputs:

Triangle 0: (9, 9, 9), (9, 9, 9), (9, 9, 9)
Triangle 0: (4.03359e-316, 4.03364e-316, 4.03364e-316), (9, 9, 9), (9, 9, 9)

valgrind --leak-check=full -s returns with no errors or warnings.

Then rewriting the Triangle3 class to not use const references:

class Triangle3 {
private:
public:
    Vec3 a, b, c;
    Triangle3(const Vec3& a, const Vec3& b, const Vec3& c) : a(a), b(b), c(c) {}
    Triangle3() : a(Vec3()), b(Vec3()), c(Vec3()) {}
};

This returns the correct results:

Triangle 0: (0, 0, 0), (0, 0, 0), (0, 0, 0)
Triangle 0: (0, 0, 0), (0, 0, 0), (0, 0, 0)

Anybody know what is causing this? I heavily suspect the const references are not working as advertised but why?


Solution

  • The issue here is that when you are push_backing to the vector, you are making a copy of the Triangle. When this happens, the references are still referencing the elements of the original Triangle, which then gets destroyed, and you are then referencing invalid elements. This is Undefined Behaviour. This also happens (again) whenever the vector needs to resize.

    Adding a copy-constructor will solve the issue:

    Triangle3( const Triangle3& o ) : _a(o.a), _b(o.b), _c(o.c){}
    

    However, this is still a rather dangerous approach, and you also need operator=.