0

What would make a field variable become obsolete before entering the destructor upon deletion of the object? I was a looking for an answer for this problem I'm having on this site and came across this: Lifetime of object is over before destructor is called?

Something doesn't add up at all: if I've declared a pointer to SomeClass inside another WrapperClass, when I construct the WrapperClass I need to create a new SomeClass and delete it on destruction of the wrapper. That makes sense and has worked so far. The pointer is still valid and correct well into the destructor otherwise obviously I wouldn't be able to delete it.

Now my problem is that my field members (both an int and a pointer to a SomeClass array) of WrapperClass are garbage when I call the destructor. I've checked the wrapper object just after construction and the data is fine. The wrapper is actually a pointer in another Main class and the problem occurs when I destruct that Main (which destructs the wrapper) but works fine if I just delete the wrapper from another method in Main. My paranoia led me to the above mentioned answer and now I'm totally confused. Anybody care to shed some light on what's really going on here?

EDIT: Node is the SomeClass.

class WrapperException{};
class Wrapper {
private:
    struct Node { /*....*/ };
    int numNodes;
    Node** nodes;
public:
    Wrapper() : numNodes(0) { nodes = new Node*[SIZE]; }
    Wrapper(const Wrapper& other) { throw WrapperException(); }
    Wrapper& operator=(const Wrapper& other) { throw WrapperException(); }
    ~Wrapper() { //calling delete Main gets me here with garbage for numNodes and nodes
        for(int i = 0; i < numNodes; i++)
            delete nodes[i];
        delete nodes;
    }
};

class MainException{};
class Main {
public:
    Main() { wrapper = new Wrapper(); }
    Main(const Main& other) { throw MainException(); }
    Main& operator=(const Main& other) { throw MainException(); }
    ~Main() { delete wrapper; }
private:
    Wrapper* wrapper;
};
Community
  • 1
  • 1
Itamar Bitton
  • 777
  • 2
  • 7
  • 25
  • 6
    You are not the only one confused. Showing us some code instead of describing it might help (Only relevant parts please). But just a thought: You did not break the rule of three, did you? – Grizzly Jan 10 '12 at 16:00
  • If you have a non-trivial destructor, did you make sure the resource is initialized in all constructors? Also: code, please. – wilhelmtell Jan 10 '12 at 16:04
  • @Grizzly I'm not an expert on the rule of 3 but I don't think it even applies in this very simple case does it? – Itamar Bitton Jan 10 '12 at 16:14
  • Run in a debugger, and set a breakpoint in the `Wrapper` destructor. Then you will see if it is called twice, which is one of the problems this might be. The other thing it might be is that you somehow uses an illegal pointer, like going one step beyond the end of a list or array. – Some programmer dude Jan 10 '12 at 16:16
  • @JoachimPileborg I set a breakpoint at the destructor of `Main` which goes to the destructor of `Wrapper` immediately with garbage... – Itamar Bitton Jan 10 '12 at 16:19
  • @itchy23: Why wouldn't it? This code would still break horribly if you try to copy an instance of Wrapper. – Grizzly Jan 10 '12 at 16:19
  • @Grizzly But as it stands right now should it be the cause? I'll try adding them anyways and report back – Itamar Bitton Jan 10 '12 at 16:20
  • @itchy23: If you didn't try to copy your `Main` object it shouldn't, otherwise it is. Of course adding them as private members without a definition is ok, as is explicitely delering them in c++11 (so trying to call them is a compilation error), but having copy constructor/assignment op in a nonworking state is kind of dangerous – Grizzly Jan 10 '12 at 16:23
  • @Grizzly: You should have added an answer by now. That is definitely the problem. – Benjamin Lindley Jan 10 '12 at 16:25
  • @BenjaminLindley Where exactly does the copy or op= assignment happen that's causing this? – Itamar Bitton Jan 10 '12 at 16:27
  • How should we know it, we haven't seen enough code to answer that. @BenjaminLindley: I'm starting to think so too – Grizzly Jan 10 '12 at 16:35
  • @itchy23: I can't see all your code, but does it matter? Your classes are wrong, period. Even if no object is copied, your classes are still wrong. – Benjamin Lindley Jan 10 '12 at 16:35
  • @BenjaminLindley What do you mean by wrong besides the already mentioned rule of 3 and missing `delete nodes;` in the destructor? – Itamar Bitton Jan 10 '12 at 16:42
  • @itchy23: I mean nothing besides that. A class that does not properly manage its resources(usually by adhering to the rule of 3) is wrong. – Benjamin Lindley Jan 10 '12 at 16:43
  • @BenjaminLindley Oh ok. I'm glad that's basically all. Thanks – Itamar Bitton Jan 10 '12 at 16:45
  • Ok so I added the copy and op= methods that throw an exception but I'm STILL getting garbage straight after calling `delete Main` – Itamar Bitton Jan 10 '12 at 16:54
  • @itch23: Then maybe you should show us some more code, since other then the rule of three violation there really isn't much to go on. Besides from your edit `Main` still violates the rule of three. And instead of throwing constructor/op= itsbetter to have none (so put `Wrapper(const Wrapper& other);` and `Wrapper& operator=(const Wrapper& other);` as **private** members of your class (and don't implement them). That way the compiler will tell you if you try using them. – Grizzly Jan 10 '12 at 17:03
  • @itchy23: You didn't make any changes to Main. – Benjamin Lindley Jan 10 '12 at 17:08
  • `Main` rule of 3 also doesn't help me apparently. I'm sure most people would scream "RAPE!" if I uploaded ALL the code... I'll try to recreate it on a simplified version and upload that. – Itamar Bitton Jan 10 '12 at 17:12
  • I've just run my code with a modified main.cpp (as the one I was using was supplied for the assignment) and it works so I'm assuming it's somehow a problem because of their code but I haven't got a clue why. Thanks to everyone for their help – Itamar Bitton Jan 10 '12 at 18:13

4 Answers4

2

You need to use the Standard library to implement this behaviour.

class Wrapper {
private:
    struct Node { /*....*/ };
    int numNodes;
    std::vector<std::unique_ptr<Node>> nodes;
public:
    Wrapper() : numNodes(0) { nodes.resize(SIZE); }
    // No explicit destructor required
    // Correct copy semantics also implemented automatically
};

class Main {
public:
    Main() : wrapper(new Wrapper()) {}
    // Again, no explicit destructor required 
    // Copying banned for move-only class, so compiler tells you
    // if you try to copy it when you can't.
private:
    std::unique_ptr<Wrapper> wrapper;
};

This code is guaranteed to execute correctly. When in C++, if you have used new[], delete or delete[], then immediately refactor your code to remove them, and review three times any use of non-placement new- constructing a unique_ptr is pretty much the only valid case. This is nothing but a common, expected outcome of manual memory management.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Unfortunately this is homework so STL is a no-go. Now if I can just understand where the copy assignment that is ruining this is happening I can learn to foresee it next time around. – Itamar Bitton Jan 10 '12 at 16:33
  • @itchy23: If you can't use the STL, then implement something similar and then use that. You *cannot* create reliable code in C++ without using it's techniques. That's why it's part of the Standard. Also, if you're desperate, then just declare a copy constructor that throws an exception- the stack trace will tell you exactly where the copy came from. – Puppy Jan 10 '12 at 16:35
  • Good idea with the exception throwing. I'm not aiming for super reliable code for this assignment but you're right... – Itamar Bitton Jan 10 '12 at 16:39
  • @itch23: Why not declare the copy constructor/assignment op (possibly as private), without defining it, so the compiler/linker will tell where the copy came from. DeadMG: you might mention that your code is C++11 only and so not really practical for a lot of (most?) programmers without modification to rely on boost (which again is not always an option). So it might be premature to declare `unique_ptr` the only valid case (although I agree in principal). – Grizzly Jan 10 '12 at 16:56
  • @Grizzly: On MSVC, it will not tell you where the linker error came from. That was my first technique and now I use the exception method. And whilst it is true that my code is C++11 only, the question is tagged C++, not C++03. Being stuck with a C++03-only compiler or no Boost dependency is the kind of thing that you state in the question. – Puppy Jan 10 '12 at 17:21
  • @DeadMG: Really good to know the linker error for MSVC. But considering the fact that there are no C++11 compilers (only incomplete and/or experimental support which has to be explicitely enabled) as of yet I would think that C++03 is implied unless otherwise stated. I'm not disagreeing that when such tools are an option they should be used, but it seems a bit too early to assume that they are availible (for C++11, for boost I wouldn't assume that the dependency is wanted). – Grizzly Jan 10 '12 at 17:38
  • I didn't think I had to be that specific for what I was getting but for the record I'm using VS 2011 Developer Preview and no STL allowed. – Itamar Bitton Jan 10 '12 at 18:16
2

Since Grizzly isn't answering, I'll put this out there.

Both your Main class and your Wrapper class need properly implemented copy constructors and assignment operators. See The Rule of 3.

The problem is, if your class ever gets copied(which is easy to happen without you even realizing it), then the pointers get copied. Now you've got two objects pointing to the same place. When one of them goes out of scope, it's destructor gets called, which calls delete on that pointer, and the pointed to object gets destroyed. Then the other object is left with a dangling pointer. When it gets destroyed, it tries to call delete again on that pointer.

Community
  • 1
  • 1
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
0

The lifetime of your wrapper object has ended, but the integer and pointer sub-objects as well as the pointee are still alive. When you invoke delete on the pointer, the pointee's lifetime ends, but the pointer still remains alive. The pointer's lifetime ends after your dtor is complete.

Thus, if your members have become corrupted, there is something else afoot.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
  • My thoughts exactly. This is not the first time I've done something like this so I have no idea what I've done that's so different this time. – Itamar Bitton Jan 10 '12 at 16:16
0

Node** nodes;

should be

Node * nodes;

Also the destructor is wrong. It should be:

for(int i = 0; i < numNodes; i++)
    delete nodes[i];
delete [] nodes;

There might be other problems as well as e.g. you haven't created a copy constructor or assignment operator so that might make it so that the copy of an object then deletes the object for you.

EDIT: changed the destructor...

Cpt. Red
  • 102
  • 3
  • But I want pointers to nodes, not nodes themselves. Edited the code to reflect that in new declaration of array as well. – Itamar Bitton Jan 10 '12 at 16:22