20

After reading about copy constructors and copy assignment operators in C++, I tried to create a simple example. Though the below snippet apparently works, I am not sure whether I am implementing the copy constructor and copy assignment operator the right way. Could you please point out if there are any mistakes/improvements or a better example to understand the relevant concepts.

class Foobase
{
    int bInt;

public:
    Foobase() {}

    Foobase(int b) { bInt = b;}

    int GetValue() { return bInt;}

    int SetValue(const int& val) { bInt = val; }
};


class Foobar
{
    int var;    
    Foobase *base;      

public:
    Foobar(){}

    Foobar(int v)
    {
        var = v;        
        base = new Foobase(v * -1);

    }

    //Copy constructor
    Foobar(const Foobar& foo)
    {       
        var = foo.var;
        base = new Foobase(foo.GetBaseValue());
    }

    //Copy assignemnt operator
    Foobar& operator= (const Foobar& other)
    {
        if (this != &other) // prevent self-assignment
        {
            var = other.var;
            base = new Foobase(other.GetBaseValue());

        }
        return *this;
    }

    ~Foobar()
    {
        delete base;
    }

    void SetValue(int val)
    {
        var = val;
    }

    void SetBaseValue(const int& val)
    {
        base->SetValue(val);
    }

    int GetBaseValue() const
    {
        return(base->GetValue());
    }

    void Print()
    {
        cout<<"Foobar Value: "<<var<<endl;
        cout<<"Foobase Value: "<<base->GetValue()<<endl;

    }   

};

int main()
{
    Foobar f(10);       
    Foobar g(f);  //calls copy constructor
    Foobar h = f; //calls copy constructor

    Foobar i;
    i = f;

    f.SetBaseValue(12);
    f.SetValue(2);    

    Foobar j = f = z; //copy constructor for j but assignment operator for f

    z.SetBaseValue(777);
    z.SetValue(77);

    return 1;
}
blitzkriegz
  • 9,258
  • 18
  • 60
  • 71

3 Answers3

16

Your copy assignment operator is implemented incorrectly. The object being assigned to leaks the object its base points to.

Your default constructor is also incorrect: it leaves both base and var uninitialized, so there is no way to know whether either is valid and in the destructor, when you call delete base;, Bad Things Happen.

The easiest way to implement the copy constructor and copy assignment operator and to know that you have done so correctly is to use the Copy-and-Swap idiom.

Community
  • 1
  • 1
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    +1 good point the copy and swap idiom is essential to have an exception safe implementation that guarantees the object to remain in a consistent state. – jdehaan Jan 15 '11 at 17:28
  • Won't the memory be freed by destructor `~Foobar()`? – blitzkriegz Jan 15 '11 at 17:28
  • @Mahatma: `~Foobar()` won't be called; the object is never destroyed, it is assigned to. – James McNellis Jan 15 '11 at 17:30
  • 1
    @Mahatma the destructor _would_ be called if you'd have a garbage collector, in which case the destructor would be called in some not-necessarily-deterministic moment, or if the pointer would be a smart pointer. But you don't have a garbage collector and the pointer most certainly is not smart. So you must clean up manually. Or use a smart pointer, of course, which is an excellent idea. – wilhelmtell Jan 15 '11 at 17:43
  • Ok, since I do a `base = new Foobase(other.GetBaseValue());`, the original memory area `base` was pointing to leaks. But if I do a `delete base;` before the new allocation to free the original memory, I get a `double free or corruption` error. – blitzkriegz Jan 15 '11 at 17:50
  • @James Will the default constructor problem be solved if I put `var = SOMEVALUE;` and `base = NULL;` in the constructor? – blitzkriegz Jan 15 '11 at 18:14
2

Only Foobar needs a custom copy constructor, assignment operator and destructor. Foobase doesn't need one because the default behaviour the compiler gives is good enough.

In the case of Foobar you have a leak in the assignment operator. You can easily fix it by freeing the object before allocating it, and that should be good enough. But if you ever add a second pointer member to Foobar you will see that that's when things get complicated. Now, if you have an exception while allocating the second pointer you need to clean up properly the first pointer you allocated, to avoid corruption or leaks. And things get more complicated than that in a polynomial manner as you add more pointer members.

Instead, what you want to do is implement the assignment operator in terms of the copy constructor. Then, you should implement the copy-constructor in terms of a non-throwing swap function. Read about the Copy & Swap idiom for details.

Also, the default constructor of Foobar doesn't default-initialize the members. That's bad, because it's not what the user would expect. The member pointer points at an arbitrary address and the int has an arbitrary value. Now if you use the object the constructor created you are very near Undefined Behaviour Land.

wilhelmtell
  • 57,473
  • 20
  • 96
  • 131
  • If I do a `delete base;` before the new allocation to free the original memory, I get a `double free or corruption` error. I'll look into the idiom. I didn't get the last part about problem with empty default constructor. Should I have put `var = SOMEVALUE;` and `base = NULL;` there since in my example, I use the parameterized constructor. – blitzkriegz Jan 15 '11 at 18:02
  • The absence of parentheses in the *new-expression* only makes a difference for types without constructors, which none of the types here are. *default-initialization* of types with at least one user-defined constructor does generate a call to the default constructor. – Ben Voigt Jan 15 '11 at 18:52
  • Pedantically, the default constructor DOES *default-initialize* the members (by not having a *ctor-initializer*, they are not mentioned in any *mem-initializer-id*, and the standard specifies that they are *default-initialized* in this case). But default initialization of an `int` leaves the state unspecified. – Ben Voigt Jan 15 '11 at 18:56
1

I have a very simple patch for you:

class Foobar
{
  int var;    
  std::unique_ptr<FooBase> base;

...

That should get you started.

The bottom line is:

  1. Don't call delete in your code (Experts see point 2)
  2. Don't call delete in your code (you know better...)
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Depending on the age of the C++ library provided with the compiler, it could be `std::tr1::unique_ptr`... – Ben Voigt Jan 15 '11 at 18:49