0

I made a minimal example:

#include <iostream>
#include <conio.h>
using namespace std;

// skipped getters and setters and bounds checking for brevity

struct Vertex {
    int x,y;

    Vertex() {
    }

    Vertex(int x, int y) {
        this->x = x;
        this->y = y;
    }

};

struct Polygon {
    Vertex *vertexlist;

    Polygon() {
    }

    Polygon(Vertex *v) {
        vertexlist = new Vertex[4]; //hard coded 4 vertices for example brevity
        for(int i=0;i<4;i++) {
            vertexlist[i] = v[i];
        }
    }

    Vertex& getVertex(int index) const {
        return this->vertexlist[index];
    }
};

struct PolyList {
    Polygon *polylist;
    int lastpoly;

    PolyList() {
        polylist = new Polygon[10]; //hard coded 10 for example brevity
        lastpoly = 0;
    }

    void add(const Polygon& p) {
        polylist[lastpoly++] = p;
    }
};

ostream& operator<<(ostream& o, Vertex& v) {
    return o << "(" << v.x << ", " << v.y << ")";
}

ostream& operator<<(ostream& o, const Polygon& p) {
    for(int i=0;i<4;i++) {
        o << p.getVertex(i) << ", ";
    }
    return o;
}

ostream& operator<<(ostream& o, PolyList& pl) {
    for(int i=0;i<pl.lastpoly;i++) {
        o << pl.polylist[i] << endl;
    }
    return o;
}

int someFunc() {
    Vertex *vl = new Vertex[4];
    PolyList pl;

    vl[0] = Vertex(1,2);
    vl[1] = Vertex(3,4);
    vl[2] = Vertex(5,6);
    vl[3] = Vertex(7,8);

    pl.add(Polygon(vl)); // this Polygon goes out of scope after this line

    cout << pl << endl;
}

int main() {
    someFunc();
}

(So tl;dr, Polygon is a list of 4x Vertex, and PolyList is a list of Polygon:s. Polygon:s are add()ed to PolyList by instantiating a temporary Polygon)

Now, this leaks memory, because the Vertices in Polygon are never freed. However, if I add a destructor: Polygon::~Polygon () {delete [] vertices} then cout << pl << endl; will not work because the Polygon has gone out of scope and the destructor frees the vertices.

I could have the PolyList destructor call a Polygon->free() function. Alternatively, I could have the Polygon::Polygon(Vertex *v) deep copy all the vertices in v. Then PolyList::PolyList(Polygon &p) could deep copy p.

I could also make a PolyList::createPolygon(int x1, int y1, int x2, int y2...) but that flies in the face of OO.

What is the proper way to handle this kind of situation in C++? Never mind my actual example where a memory leak would not be a problem, I'm talking in principle. If I make an hierarchical object tree, I want to copy the pointers, not deep copy the objects.

EDIT: I'm trying to learn C++ on a deep level, so this is not about using vector<> or another "canned solution"; that is not what I'm after here, though I'm sure that is a good solution if the above example was an actual problem I was having. The example above is just the briefest example I could think of to explain my question.

  • 4
    Learn to use `std::vector`, and your problems will go away. – PaulMcKenzie Aug 25 '14 at 18:15
  • Your title mentions avoiding copies, but your actual question deals with avoiding both memory leaks and double deletion, and the Rule of Three information I've linked will provide all you want to know about that. – Ben Voigt Aug 25 '14 at 18:35
  • For additionally avoiding extra copies, read about the C++11 [Rule of Five](http://stackoverflow.com/q/4782757/103167) – Ben Voigt Aug 25 '14 at 18:36
  • Ben Voigt: yes, I should have clarified, but depending on what the answer turns out to be, I'm not really sure what my question is :/ The sore thumb for me is that I can't easily just copy the pointers without adding a bunch of memory management code. Perhaps there is no simpler solution (apart from using somebody elses memory management code like in std::vector) but since I'm a noob when it comes to C++ and have nobody to guide me (professor knows nothing) I wanted to ask if there is anything obvious I am missing. I will read up on the rules of 3 && 5. – Jonathan J. Bloggs Aug 25 '14 at 18:48
  • `std::vector` isn't "somebody else's memory management code". It is part of the C++ language. Use it. The *worst* that can happen is that it'll teach you a few things about how your own classes should behave with regards to memory management. – jalf Aug 25 '14 at 19:42
  • jalf: I'm beginning to think you are right, and that there really exists no simpler way (as in less complex code "in total", including the code of std::vector) than `std::vector` or the likes, when dealing with dynamic arrays in C++. I always assumed there was, but perhaps there cannot be without garbage collection (or going back to pure C). – Jonathan J. Bloggs Aug 25 '14 at 22:03

2 Answers2

1

You could use smart pointers and STL containers (mainly std::vector as suggested by PaulMcKenzie).

They will help a lot.

Your example using std::vector

#include <iostream>
#include <conio.h>

#include <vector>
using namespace std;

// skipped getters and setters and bounds checking for brevity
struct Vertex {
    int x, y;

    Vertex() {
    }

    Vertex(int x, int y) {
        this->x = x;
        this->y = y;
    }
};

typedef vector<Vertex> vertex_list_t;

struct Polygon {
    vertex_list_t vertexlist;

    Polygon() {
    }

    Polygon(vertex_list_t v) {
        //hard coded 4 vertices for example brevity
        for (int i = 0; i<4; i++) {
            vertexlist.push_back(Vertex(i, i));
        }
    }

    Vertex getVertex(int index) const {
        return vertexlist[index];
    }
};

typedef vector<Polygon> polygon_list_t;

ostream& operator<<(ostream& o, Vertex& v) {
    return o << "(" << v.x << ", " << v.y << ")";
}

ostream& operator<<(ostream& o, const Polygon& p) {
    for (auto v: p.vertexlist) {
        o << v << ", ";
    }
    return o;
}

ostream& operator<<(ostream& o, polygon_list_t& pl) {
    for (auto &p : pl) {
        o << p << endl;
    }
    return o;
}

int someFunc() {

    vertex_list_t vl = {
        Vertex(1, 2),
        Vertex(3, 4),
        Vertex(5, 6),
        Vertex(7, 8)
    };

    polygon_list_t pl;

    pl.push_back(Polygon(vl)); // this Polygon goes out of scope after this line
    cout << pl << endl;
    return 0;
}

int main() {
    someFunc();
}

What's the real deal?

In the line

pl.add(Polygon(vl)); // this Polygon goes out of scope after this line

you pass the polygon as a temporary and:

$12.2/3- "Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception."

change that line by:

Polygon p1(vl);
pl.add(p1); // this Polygon NOT goes out of scope after this line
Raydel Miranda
  • 13,825
  • 3
  • 38
  • 60
  • Ok, but is that the best solution if I want to learn what is going on at the lowest level? Is the problem of initialization and object redundancy only solved by something like std::vector, or is there a simpler answer that only does precisely what my question is about? Not saying that std::vector would be too complex for general use or anything, I just feel that I'm not understanding C++ well enough if I can't figure this out without using the STL. – Jonathan J. Bloggs Aug 25 '14 at 18:30
  • 1
    @JonathanJ.Bloggs `Ok, but is that the best solution if I want to learn what is going on at the lowest level` Here's the question to you: Are you writing a polygon class, or a dynamic array class? If it's the former, then use ready-made components to reach your goal. If it's the latter, then don't obfuscate the learning curve by writing a polygon class -- write a dynamic array class. – PaulMcKenzie Aug 25 '14 at 18:33
  • 1
    I'm writing neither... I'm just writing some code for a basic class on C++, and the whole thing with `.add(Polygon(v))` going out of scope made me realize I don't know what the proper way to do something like this is.The course material either leaks memory (no problem; very short programs) or deep copies the temporary objects. Both of those feels wrong to me, and using `std::vector` or `std::smart_ptr` makes me not understand what is going on under the hood. But perhaps I should just settle for the STL solution for now and revisit this problem when I'm ready to write a dynamic array class. – Jonathan J. Bloggs Aug 25 '14 at 18:44
  • @JonathanJ.Bloggs - That's the problem with C++ courses like this -- they give you bad examples and pass them off as "good examples" As you noticed, you leaked memory with the example. The goal should be to write bug-free programs, not buggy ones. Giving assignments like this does nothing in terms of learning proper C++, instead it gives the false sense of accomplishment. Now, if you were given the assignment of writing your own dynamic array or string class, where the focus is getting copying and memory leaks under control, then that's a different story. – PaulMcKenzie Aug 25 '14 at 18:50
  • @JonathanJ.Bloggs answer edited, check last part. – Raydel Miranda Aug 25 '14 at 18:53
  • After reading the Rule of 5 [http://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11] I sense that my original question about useless copying of objects is not a completely trivial question in C++, and that the STL solutions don't solve this problem, instead just simplifying memory management? Coming from JavaScript with managed memory and closures, this is a very confusing part of C++ to me, more so because it is so simple to understand in C where nothing is "hidden" from the programmer or debugger. – Jonathan J. Bloggs Aug 25 '14 at 18:56
  • Raydel Miranda: you edited your answer after I answered. Indeed, doing away with the temporary objects makes everything much easier to understand; it just "doesn't feel like the C++ way". But perhaps that is because I'm used to seeing code that uses various clever array and managed pointer classes from the STL. – Jonathan J. Bloggs Aug 25 '14 at 19:03
  • @JonathanJ.Bloggs `I sense that my original question about useless copying of objects is not a completely trivial question in C++` First, an optimizing C++ compiler can and will eliminate copies of objects when it sees fit. Therefore the number of "useless copies" cannot be predetermined. However, your code using excessive pointers is a cause for the compiler to *not* optimize, as many times, pointer aliasing stops the optimizer in its tracks. Writing higher-level C++ code allows the compiler to do inlining and copy elision. – PaulMcKenzie Aug 25 '14 at 19:16
  • 1
    @JonathanJ.Bloggs `Coming from JavaScript with managed memory and closures, this is a very confusing part of C++ to me, more so because it is so simple to understand in C where nothing is "hidden" from the programmer or debugger` C and C++ are two different languages. What you learn in C doesn't mean it is the way you should do things in C++. – PaulMcKenzie Aug 25 '14 at 19:21
0

You can use shared_ptrs as the solution. i.e.

#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <memory>
#include <list>
#include <vector>

using namespace std;

struct Vertex
{
    int x,y;
    Vertex() : x(0), y(0)
    {
    }

    Vertex(int _x, int _y)
    {
        x = _x;
        y = _y;
    }
};

struct Polygon
{
    vector<Vertex> vertexes;

    Polygon()
    {
    }

    Polygon(Vertex *v)
    {
        const int ELEMS_COUNT = 4;
        vertexes.reserve(ELEMS_COUNT);
        vertexes.insert(vertexes.end(), v, v + ELEMS_COUNT);
    }

    Vertex getVertex(int index) const
    {
        return vertexes[index];
    }
};

typedef shared_ptr<Polygon> PolygonPtr;

struct PolyList
{
    std::list<PolygonPtr> polylist;
    void add(PolygonPtr p)
    {
        polylist.push_back(p);
    }
};

ostream& operator<<(ostream& o, const Vertex& v) {
    return o << "(" << v.x << ", " << v.y << ")";
}

ostream& operator<<(ostream& o, const Polygon& p) {
    for (auto& p : p.vertexes)
    {
        o << p << ", ";
    }
    return o;
}

ostream& operator<<(ostream& o, PolyList& pl) {
    for(auto& p : pl.polylist)
    {
        o << *p << endl;
    }
    return o;
}

int someFunc() {
    Vertex vl[] = {Vertex(1, 2), Vertex(3, 4), Vertex(5, 6), Vertex(7, 8)};
    PolyList pl;
    pl.add(PolygonPtr(new Polygon(vl)));

    cout << pl << endl;
    return 0;
}

int main()
{
    someFunc();
    return 0;
}
Dean
  • 86
  • 4
  • You remove the comment just when I was writing about that ;p – Raydel Miranda Aug 25 '14 at 18:59
  • it was my sneak plan ;) – Dean Aug 25 '14 at 19:01
  • Dean: one question though. Why should `getVertex` return a copy of the `Vertex` and not a reference to the original `Vertex` (as in `Vertex& getVertex(int index) const`) ? – Jonathan J. Bloggs Aug 25 '14 at 23:52
  • Well, it's always a philosophical question. What should return getter, a reference or a copy of object. If you want to return a reference, you should write "mutable" modificator to the vertexes, because getVertex has "const" modificator. From the other point, is it correct to make 'getVertex' to be const, if it returns reference that can be changed? So the ideal case to have a getter and setter. Getter returns copy(to leave data unchanged), Setter changes the specified vertex – Dean Aug 26 '14 at 07:37