tags:

views:

359

answers:

7

Sorry for the basic question, but I'm having trouble finding the right thing to google.

#include <iostream>
#include <string>
using namespace std;

class C {
public: 
C(int n) {
    x = new int(n);
}

~C( ) {
    delete x;
}

int getX() {return *x;}

private: 
    int* x; 
}; 

void main( )  {
    C obj1 = C(3);
    obj1 = C(4);
    cout << obj1.getX() << endl;
}

It looks like it does the assignment correctly, then calls the destructor on obj1 leaving x with a garbage value rather than a value of 4. If this is valid, why does it do this?

+1  A: 

If there is a class C that has a constructor that takes an int, is this code valid?

C obj1(3);
obj1=C(4);

Assuming C has an operator=(C) (which it will by default), the code is valid. What will happen is that in the first line obj1 is constructed with 3 as a the parameter to the constructor. Then on the second line, a temporary C object is constructed with 4 as a parameter and then operator= is invoked on obj1 with that temporary object as a parameter. After that the temporary object will be destructed.

If obj1 is in an invalid state after the assignment (but not before), there likely is a problem with C's operator=.

Update: If x really needs to be a pointer you have three options:

  1. Let the user instead of the destructor decide when the value of x should be deleted by defining a destruction method that the user needs to call explicitly. This will cause memory leaks if the user forgets to do so.
  2. Define operator= so that it will create a copy of the integer instead of a copy of the value. If in your real code you use a pointer to something that's much bigger than an int, this might be too expensive.
  3. Use reference counting to keep track how many instances of C hold a pointer to the same object and delete the object when its count reaches 0.
sepp2k
I think you're probably right. I probably need to implement operator=(C) properly for the dynamic memory stuff I have going on.
Rob Lourens
This is wrong the code is broken.
Martin York
See here for an example of why it is broken: http://stackoverflow.com/questions/255612/c-dynamically-allocating-an-array-of-objects/255744#255744
Martin York
@Martin York: The original question was whether `C obj1(3); obj1 = C(4);` was valid C++ code. Everything in my post before the "Update:" part answers that question.
sepp2k
@sepp2k: Makeing it compile is not enough to make it valid! Valid would be code that does not have undefined behavior. The code above has undefined behavior because it does a double delete on a pointer. So your post is not correct this is an invalid program as it has undefined behavior.
Martin York
@Martin York: The original question did not have any pointers. It contained the two lines of code I quoted and nothing else.
sepp2k
A: 

When are you testing the value of obj1? Is it after you leave the scope?

In your example, obj1 is a stack object. That means as soon as you leave the function in which it defined, it gets cleaned up (the destructor is called). Try allocating the object on the heap:

C *obj1 = new C(3);
delete obj1;
obj1 = new C(4);
David Crawshaw
+1  A: 

NEW: What's happening is that your destructor has deallocated the memory allocated by the constructor of C(4). So the pointer you have copied over from C(4) is a dangling pointer i.e. it still points to the memory location of the deallocated memory

class C {
public: 
C(int n) {
    x = new int(n);
}

~C( ) {
    //delete x; //Don't deallocate
}

void DeallocateX()
{
 delete x;
}

int getX() {return *x;}

private: 
    int* x; 
}; 

int main(int argc, char* argv[])
{
    // Init with C(3)
    C obj1 = C(3);
    // Deallocate C(3)
    obj1.DeallocateX();
    // Allocate memory and store 4 with C(4) and pass the pointer over to obj1
    obj1 = C(4);
    // Use the value
    cout << obj1.getX() << endl;
    // Cleanup
 obj1.DeallocateX();


 return 0;
}
Jacob
Why does it deallocate the memory allocated by the constructor of C(4)? Shouldn't it deallocate the memory allocated by C(3), then use the constructor for C(4)?
Rob Lourens
Yeah you're right, it uses the destructor of C(3) first, then the constructor of C(4) and then again the destructor of C(4) since it's out of scope. So now, obj1::x points to the address of the memory allocated by C(4) but it has been deallocated by its destructor, so it points to garbage. Hence, my suggestion of manually deallocating memory.Did you try running the code? I'm sure it works :)
Jacob
So to answer your question, yes it does deallocate C(3) and then use C(4) but then the original code with the destructor deallocates C(4) as well so now x points to garbage.
Jacob
I understand, thanks.
Rob Lourens
+2  A: 

If C contains a pointer to something, you pretty much always need to implement operator=. In your case it would have this signature

class C
{    
    public:
    void operator=(const C& rhs)
    {
        // For each member in rhs, copy it to ourselves
    } 
    // Your other member variables and methods go here...
};
Tom Leys
You also pretty much have to implement the copy constructor. Implementing the assignment operator in terms of the copy constructor is relatively trivial.
Martin York
+2  A: 

I do not know enough deep, subtle C++ to explain the problem you are encountering. I do know, however, that it's a lot easier to make sure a class behaves the way you expect if you follow the Rule of Three, which the code you posted violates. Basically, it states that if you define any of the following you should define all three:

  • Destructor
  • Copy constructor
  • Assignment operator

Note as well that the assignment operator implementation needs to correctly handle the case where an object is assigned to itself (so-called "self assignment"). The following should work correctly (untested):

#include <iostream>
#include <string>
using namespace std;

class C {
public: 
C(int n) {
    x = new int(n);
}

C(const C &other): C(other.getX()) { }

~C( ) {
    delete x;
}

void operator=(const C &other) {
    // Just assign over x.  You could reallocate if you first test
    // that x != other.x (the pointers, not contents).  The test is
    // needed to make sure the code is self-assignment-safe.
    *x = *(other.x);
}

int getX() {return *x;}

private: 
    int* x; 
}; 

void main( )  {
    C obj1 = C(3);
    obj1 = C(4);
    cout << obj1.getX() << endl;
}
Michael E
Thanks, this is also just what I needed to know, although I had to change the copy constructor to have a body of *x = other.getX();
Rob Lourens
So if the copy constructor and assignment operator do basically the same thing, is there any way to reuse one to implement the other?
Rob Lourens
Your implementation is not memory safe. Because operator= does not make a deep copy, the pointer to x is deleted by the destructor of the temporary object. I.e C a(4); C b(5); a=b; - the original a.x is leaked, and b.x is deleted by both a and b when they pass out of scope.
Tom Leys
@Tom: Isn't just the value of the int (not the pointer to int) copied over by the value of the other int (since the value is assigned to *x and not x)? Since no pointer is copied over, there's no leak.
Tobias Langner
@Tobias - true, the *x does indeed make this a deep copy. My bad.
Tom Leys
+1  A: 

Be explicit about ownership of pointers! auto_ptr is great for this. Also, when creating a local don't do C obj1 = C(3) that creates two instances of C and initializes the first with the copy constructor of the second.

Heed The Guru.

class C {
public:
   C(int n) : x(new int(n)) { }
   int getX(){ return *x; }
   C(const C& other) : x(new int(*other.x)){}
   C& operator=(const C& other) { *x = *other.x; return *this; }
private:
   std::auto_ptr<int> x;
};

int main() {
   C obj1(3);
   obj1 = C(4);
   std::cout << obj1.getX() << std::endl;
}
joshperry
So close but no biscuit. Your assignment operator leaves you with two auto pointers holding the same pointer. You will get double deletion.
Martin York
The dereference operators on the lhs and rhs auto_ptr cause a copy of the value from the address of the rhs, to the address on the lhs, not a copy of the addresses themselves.
joshperry
+1  A: 

Basically you are trying to re-implement a smart pointer.
This is not trivial to get correct for all situations.

Please look at the available smart pointers in the standard first.

A basic implementation (Which will fail under certain situations (copy one of the standard ones to get a better one)). But this should cover the basics:

class X
{
    int*     data;

    public:
      // Destructor obvious
      ~X()
      {
          delete data;
      }
      // Easy constructor.
      X(int x)
          :data(new int(x))
      {}
      // Copy constructor.
      // Relatively obvious just do the same as the normal construcor.
      // Get the value from the rhs (copy). Note A class is a friend of
      // itself and thus you can access the private members of copy without
      // having to use any accessor functions like getX()
      X(X const& copy)
          :data(new int(copy.x))
      {}
      // Assignment operator
      // This is an example of the copy and swap idiom. This is probably overkill
      // for this trivial example but provided here to show how it is used.
      X& operator=(X const& copy)
      {
          X tmp(copy);
          this->swap(tmp);
          return this;
      }
      // Write a swap() operator.
      // Mark it is as no-throw.
      void swap(X& rhs) throws()
      {
          std::swap(data,rhs.data);
      }

  };
Martin York