3

I have a little trouble with a destructor. In its current state, it create a segfault. Please note that the destructor is only implemented and never explicitly called anywhere. The segfault appear no matter where the breakpoints are.

Here is the destructor :

Graph::~Graph() {

    while(!children.empty()) {

        for(vector<Graph*>::iterator itr = children.begin(); itr != children.end(); ++itr) {
            delete *itr;
        }
        children.clear();
        delete parent;
        delete tab;
    }
}

I also did a variation like this, without better results :

Graph::~Graph() {

    while(!children.empty()) {

        for(unsigned i = 0; i < children.size(); i++)
        {
            delete children.at(i);
        }
        children.clear();
        delete parent;
        delete tab;
    }
}

Here is the class declarations :

class Graph
{
    private :
            Graph*                      parent;
            vector<Graph*>              children;
            Board*                      tab;

    public :
                                    Graph(Board);
                                    Graph(Board, Graph*);
                                    ~Graph();
                void                AddNode(Board&);

          //      Graph&            BFS(Graph&); 
                Graph&              operator=(Graph source);
                vector<Graph*>&     getchildren();
                Graph*              getparent();
                Board*              gettab();
    };



class Board {

private :
            int**           tab;
            int             nbline;
            int             nbcolumn;
            Position        emptyspot;

public  :
                            Board();
                            Board(int, int, Play&);
                            Board(int, int);
                            Board(const Board&);
                            Board(int, int, ifstream&);
                            ~Board();
            void            setValue(Position&, int);
            void            setNbline(int m);
            void            setNbcolumn(int n);
            int             getValue(Position&);
            int             getNbline();
            int             getNbcolumn();
            int             getEmptyline();
            int             getEmptycolumn();
            void            setEmptySpot(Position&);
            Position&       getEmptySpot();

            Board&          operator=(Board& source);

        };


Board::~Board()
{
    for(int i = 0; i < this->nbline; i++)
    {
        delete tab[i];
    }
    delete tab;
}

I'm not really comfortable and very inexperienced with debugger so i don't really know how to use it properly. The call stack point at this line is stl_vector.h :

  /**
   *  Returns a read-only (constant) iterator that points one past
   *  the last element in the %vector.  Iteration is done in
   *  ordinary element order.
   */
  const_iterator
  end() const _GLIBCXX_NOEXCEPT
  { return const_iterator(this->_M_impl._M_finish); }

I don't know what these lines mean to be honest.

The call stack also show the while loop line in the debugger, with the note : Graph::~Graph(this=0x90909090, __in_chrg=optimized out). I also point 3 times the line delete *itr (with the same note).

So my question is, how can i destruct my Graph object ? :'(

EDIT : after further experimentations, the segfault disappear when i delete the only linein code that add things in the vector. Here is the method. I'll add that the values in the vector are always the sames (shouldn't be).

void    Graph::AddNode(Board& tablo)
{
        Graph tmp(tablo, this);
        Graph* newChild = &tmp;
        children.push_back(newChild);  // if i commend this, no segfault
}

I don't know if this is two different problems or the push_back is the cause of the destructor malfunction. I think it's non-related, i expected the segfault to disappear (of course destructor don't have trouble to destruct the tree if the tree only got one node).

EDIT2 : This code doesn't create any segfault, but it doesn't truly destruct all the vectors in the vectors, right ? I think it doesn't because erase will just destroy pointers and not the objets themselves.

while(!children.empty()) {
        children.erase(children.begin(),children.end());
        delete parent;
        delete tab;
    }

Moreover, with this, sometimes the program seem to execute well but don't stop at the end of execution. Debbuger doesn't seem to find anything

EDIT : as ask, the copy constructor of Graph :

Graph::Graph(const Graph& source) {*this = source;}

Graph& Graph::operator=(Graph source)
{
     if(this!=source)
    {
        this->parent = source.parent;
        this->tab = source.tab;
//    this->seen = source.seen;


      while(!source.children.empty()) {

        for(unsigned i = 0; i<source.children.size(); i++) {
            this->children.at(i) = source.children.at(i);
            }
      }
    }

    return *this;
}
Csi
  • 526
  • 3
  • 22
  • Where is your `Graph` user-defined copy constructor? Also, post a small program that demonstrates how you're using `Graph`. – PaulMcKenzie Apr 27 '15 at 15:30
  • I got one. Graph::Graph(const Graph& source) : parent(source.parent), tab(source.tab) { while(!source.children.empty()) { for(unsigned i = 0; ichildren.at(i) = source.children.at(i); } } – Csi Apr 27 '15 at 15:32
  • You should maintain a strict parent child relationship. Otherwise you will run into circular relations leading to problems (like double deletions) –  Apr 27 '15 at 15:32
  • Please add the code to the post, not in the comment. Also, post your assignment operator. Also, `int** tab;` Why did you resort to using this instead of `std::vector>`? – PaulMcKenzie Apr 27 '15 at 15:32
  • Please pair new/delete calls (or delegate the delete to a smart pointer). What you do with `Graph* newChild = &tmp;` is wrong –  Apr 27 '15 at 15:44
  • @Csi You may also want to check that call to `delete tab;` in your code. I bet it should be `delete [] tab;` due to `tab` being an `int**`. Also, it is almost by convention that the copy constructor is the one with the bulk of the work, and the assignment operator uses the copy constructor and destructor as helpers to do the assignment (the copy/swap idiom). Your code has it backwards, where the assignment operator does all of the work. – PaulMcKenzie Apr 27 '15 at 15:52

5 Answers5

4

The problem is:

delete parent;

Your loop is enforcing the semantics that a Graph owns its children. But by adding that line, you are additionally adding the semantic that a Graph owns its parent. You can't have it both ways. This way, your child is going to delete your parent while it's deleting the child. Removing that delete should solve your issue.

Even better would be to simply express ownership explicitly in terms of member variables:

struct Graph {
    std::vector<std::unique_ptr<Graph>> children;
};

That way, everything will get deleted correctly without you even having to write a destructor! See Rule of Zero

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Barry
  • 286,269
  • 29
  • 621
  • 977
  • I just set delete parent as a comment, but it didn't solve the segfault. I have a question about your point though. Since parent is a pointer, Graph just point to its parent and doesn't own it, right ? I thought i was trying to delete a root, and the children were recursivly deleted before the root itself. I think i didn't understood what you meant, sorry ._. – Csi Apr 27 '15 at 15:36
  • You meant i should maintain only a parent->child relationship, as Dieter Lucking said ? – Csi Apr 27 '15 at 15:39
  • 1
    @Csi "just point to its parent and doesn't own it" - correct, except then when you `delete` it - you're expressing ownership. But it's not the child's responsibility to clean up its parent, it's the parent's responsibility to clean up its children. – Barry Apr 27 '15 at 15:47
  • @PaulMcKenzie The `tab` is a `Board*`. But also your copy constructor is only shallow-copying `tab`, so it could be that you have two `Graph`s which both try to `delete` it. – Barry Apr 27 '15 at 15:53
  • @Barry It isn't a board, it is an `int**`. Look at the destructor for `Board`. – PaulMcKenzie Apr 27 '15 at 17:08
  • @PaulMcKenzie The question is about the `Graph` destructor. – Barry Apr 27 '15 at 17:11
0

I agree with @Barry that the line

delete parent;

shouldn't be there in the destructor.

In addition, the destructor can be cleaned up a little bit.

You have the call

delete tab;

inside the while loop. The call should be there regardless of whether there are any children or not. Shouldn't it?

You can also remove the while loop altogether.

Graph::~Graph() {

   // Just loop over the children and delete them.
   // If there are no children, the loop is a no op.
   for(vector<Graph*>::iterator itr = children.begin(); itr != children.end(); ++itr) {
      delete *itr;
   }

   // No need for this.
   // The vector will be deleted anyway.
   // children.clear();


   // Delete tab regardless of the number of children.
   delete tab;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

The AddNode function seems wrong. The tmp object will be destroyed when the function completes and then the pointer in the vector gets invalid and causes the problem when you call delete later on.

Use new instead. Maybe like this:

void    Graph::AddNode(Board& tablo)
{
    Graph* newChild = new Graph(tablo, this);
    children.push_back(newChild);
}
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • I changed as you said. It doesn't solve the segfault in destructor or the fact that my vector got only the same tab value inside. But i'll keep this newChild declaration. The double ;; is on purpose ? – Csi Apr 27 '15 at 15:51
  • @Csi - the double ;; was a mistake - answer updated now. Maybe you should combine my suggestion with the suggestion given by Barry. Meanwhile I can take a second look at your code. – Support Ukraine Apr 27 '15 at 15:53
  • @Csi - There are too much code that we can't see. Try to delete as much code as possible to create a minimal example of failing code and then post all the code. Currently we can't see how Board is created, how Board.tab is created and so on. Does every Graph have its own Board? – Support Ukraine Apr 27 '15 at 16:05
  • I posted an answer to show the evlutions of the problem after your advice. I didn't see your comments asking for more code. Every graph have its own Board, yes. I'll work on that minimal failing code. – Csi Apr 27 '15 at 16:23
0

I'll sum up the modifications i did and their consequences.

I changed the destructor as @R Sahu and Barry said.

Graph::~Graph() {

     for(vector<Graph*>::iterator itr = children.begin(); itr != children.end(); ++itr) {
      delete *itr;
   }
   delete tab;
}

I changed the addNodes as nielsen adviced (so does Dieter Lücking) :

void    Graph::AddNode(Board& tablo)
{
        Graph tmp(tablo, this);
         Graph* newChild = new Graph(tablo, this);
        children.push_back(newChild);
}

And, as Paul McKenzie asked, here are my operator= and copy constructor for Graph.

Graph::Graph(const Graph& source) : parent(source.parent), tab(source.tab) {

      while(!source.children.empty()) {

      for(unsigned i = 0; i<source.children.size(); i++) {
                this->children.at(i) = source.children.at(i);
                }
      }
}

Graph& Graph::operator=(Graph source)
{
    this->parent = source.parent;
    this->tab = source.tab;
//    this->seen = source.seen;

    return *this;
}

It makes me figured out that operator= doesn't copy the vector :'(

Now, how is execution. The Segfault is still here, but the callstack changed a lot. I got only 2 lines in call stack, instead of 5/6. The callstack told me there is a problem in

Board*           Graph::gettab(){return this->tab;}

When i do this, in order to see what is in the graph.children vector :

  for(vector<Graph*>::iterator itr = children.begin(); itr != children.end(); ++itr)
            {
              tmp = (*itr)->gettab();
              Board& test = *tmp;  // it's print(Board&) so i just create this. I'll create print(int**) later

              print(test);
 }

If i put these lines in comments, the segfault is still here. The callstack quote me 3 lines in stl_iterator.h (you need those ?) and both the for loop and the delete *itr lines in the destructor.

EDIT :

@nielsen You asked for my main.

int main()
{
            int             lines, columns;
            Play            game;
            Play&           jeu = game;
            srand(time(NULL));

            cout << "+++++++++++++++++++++++++++++" << endl;
            cout << "+++     SOLVER            +++" << endl;
            cout << "+++++++++++++++++++++++++++++" << endl;
            cout << endl;
            cout << endl;


            cout << "Random Board \nLignes : ";
            cin >> lines;
            cout << "\n Colonnes : ";
            cin >> columns;

            Board           randomTab(lines, columns, jeu);

            print(randomTab);
            trace(randomTab);
            cout << "________________" << endl;

            Graph           tree(randomTab);  /// Call stack point to this !
            Board           temporary(randomTab);
            Board&          temp = temporary;
            Board*          tmp = NULL;
            bool            controls = false;

            vector<int> directions {72,75,77,80};

            for(vector<int>::iterator itr = directions.begin(); itr != directions.end(); ++itr)
            {
                temp = randomTab;
                controls = jeu.moves(temp,*itr);

                if(controls)
                {
                     cout << *itr << endl;
                     tree.AddNode(temp);
                     print(temp);
            //         trace(temp);
                     controls = false;
                }
            }

            cout << "__________________" << endl;
            vector<Graph*> children = tree.getchildren();

       /*     for(vector<Graph*>::iterator itr = children.begin(); itr != children.end(); ++itr)
            {
              tmp = (*itr)->gettab();
              Board& test = *tmp;

              print(test);
              cout << "test";
              trace(temporary);
            }*/


return 0;
}


Graph::Graph(Board source) : parent(NULL), tab(&source) {}  // constructeur pour racine

Graph::Graph(Board source, Graph* grap) : parent(grap), tab(&source) {} // constructeur pour nouvelle feuille


Graph::Graph(const Graph& source) : parent(source.parent), tab(source.tab) {  // constructeur par copie

      while(!source.children.empty()) {

      for(unsigned i = 0; i<source.children.size(); i++) {
                this->children.at(i) = source.children.at(i);
                }
      }
}
Csi
  • 526
  • 3
  • 22
  • In the AddNode function delete the line `Graph tmp(tablo, this);` It shall not be there. I know that it was in my first answer but that was simply because I forgot to delete it myself :) So just get rid of that line. However it is not the final solution though. – Support Ukraine Apr 27 '15 at 16:27
  • @Csi In your assignment operator, you should be *swapping* the members between `source` and *this*, not just merely assigning them. In addition, you failed to copy all of the members. That is a bug you need to fix. `{ std::swap(parent, source.parent); std::swap(tab, source.tab); std::swap(children, source.children); return *this; }` – PaulMcKenzie Apr 27 '15 at 16:57
  • @Csi - I see some problems. 1) The randomTab is not created by new. Still you call delete on it in the Graph destructor. That is bad. 2) Further it seems your for-loop adds the same Board (i.e. randomTab) to all the Graph instances created by AddNode. That is also a problem because the Graph destructor calls delete on the tab. So even if randomTab was created by new you would still have problems because many Graphs would try to delete the same instance. Try to remove the `delete tab` in Graph destructor. – Support Ukraine Apr 27 '15 at 18:04
  • @nielsen You're 2) explain why i always add the same value in the vector !!!! I'll try to declare randomTab with new ! And i'll pass a pointer to move() instead of a reference. My variable have a too much lifespan, so i use the same instance all the time :o – Csi Apr 27 '15 at 19:10
  • @nielsen Thanks a lot. I have no destructor issues anymore. I just can't see what is in the vectors because i have a new segfault when calling getNbline and getNbcolumns. I'll work on this. Also, if i specify a tab with different dimensions (3 lines 4 columns for exemple), programm crash instantly. I may have mess up my Board constructor. If you want to post an answer that i'll vote as answer of the problem, in order to close the ticket, go ahead. – Csi Apr 28 '15 at 14:10
0

Someone asked me how Board is created. In this current use case :

Board::Board(int m, int n, Play& jeu) : tab(new int*[m]), nbline(m), nbcolumn(n), emptyspot(n-1,m-1){

           int              x(1);

           for (int i = 0; i < m; ++i){
                tab[i] = new int[n];

                for(int j = 0; j < n; ++j) {
                    tab[i][j] = x; x++;}}

           tab[n-1][m-1]=0;
           x=0;

           while (x!=1000)
           {
            int numbers[] = { UP, DOWN, LEFT, RIGHT };
            int length = sizeof(numbers) / sizeof(int);
            int randomNumber = numbers[rand() % length];

            jeu.moves(*this, randomNumber);
            x++;
           }
        }

/// copy constructor
 Board::Board(const Board& origin): tab(NULL), nbline(origin.nbline), nbcolumn(origin.nbcolumn), emptyspot(origin.emptyspot)
        {
            this->tab = new int*[this->nbline];
            for (int i = 0; i < this->nbline; ++i)
            {
                this->tab[i] = new int[this->nbcolumn];
                for (int j = 0; j < this->nbline; ++j)
                {
                    this->tab[i][j] = origin.tab[i][j];
                }
            }
        }



Board::~Board()
{
    for(int i = 0; i < this->nbline; i++)
    {
        delete tab[i];
    }
    delete tab;
}



Board&              Board::operator=(Board& source)
{

    if (this != &source)
    {
            Position        pos(0,0);
            Position&       p=pos;

            this->setNbline(source.getNbline());
            this->setNbcolumn(source.getNbcolumn());
            this->setEmptySpot(source.getEmptySpot());

            for (int i =0; i < source.getNbline(); i++)
            {
                for(int j=0; j < source.getNbcolumn(); j++)
                {
                    p.setposition(i,j);
                    setValue(p,source.getValue(p));
                }
            }

    }
Csi
  • 526
  • 3
  • 22
  • ping @nielsen. Is that what you asked ? – Csi Apr 27 '15 at 16:44
  • I think it has to be `delete [] tab` as tab was new'ed as an array. – Support Ukraine Apr 27 '15 at 16:52
  • well, not really. If possible I think you need to show how the objects are created from main(). I guess you start by creating a Board and then create a Graph while passing the Board. Already here it seems strange that the Graph takes a Board in the constructor but has a Board pointer as member. Is that really intended? How does the Graph constructor look? How is the Board created - using new or...? – Support Ukraine Apr 27 '15 at 17:03
  • it is probably a bad/confusing idea to have a variable in Graph named `tab` as a `Board*` and having another variable named `tab` in Board as a `int**` Maybe it would be a good idea to rename one of them. – Support Ukraine Apr 27 '15 at 17:17
  • @nielsen I edidted the "sum up modification" answer i made earlier, to show the main. I put a comment on a line where the callstack talk about. But it was not the case before. The improvement you all advised me make me have a more correct code, but new lines are quoted by callstack now (still the destructor, and this line in main). – Csi Apr 27 '15 at 17:54
  • @nielsen I showed the main. The Graph receive a Board, created independantly. It's not really on purpose, but i don't need graph if i want the player to play, i only need graph for resolutions algorithms. – Csi Apr 27 '15 at 18:00