tags:

views:

338

answers:

3

Question is probably pretty basic, but can't find out what's wrong (and it leads to huge of memleaks in my app):

class MyClass {
public:
    MyClass() { cout << "constructor();\n"; };
    MyClass operator= (const MyClass& b){ 
        cout << "operator=;\n"; return MyClass(); 
    };
    ~MyClass() { cout << "destructor();\n"; };
};

main() {
 cout << "1\n";
 vector<MyClass> a;
 cout << "2\n";
 MyClass b;
 cout << "3\n";
 a.push_back(b);
 cout << "4\n";
}

The output is:

1
2
constructor();
3
4
destructor();
destructor();
  1. Why are there 2 destructors?
  2. If it's because a copy is created to be inserted into vector - how come "operator=" is never called?
+4  A: 

When the b object gets pushed onto the vector a copy is made, but not by the operator=() you have - the compiler generated copy constructor is used.

When the main() goes out of scope, the b object is destroyed and the copy in the vector is destroyed.

Add an explicit copy constructor to see this:

MyClass( MyClass const& other) {
    cout << "copy ctor\n";
};
Michael Burr
Is there a way to make that "copy" mechanism either use my operator= or (better yet) at least call a constructor?
Slava N
You can write your own copy constructor, mirroring what is done by the operator=.
aib
It calls copy constructor, just implement it and you'll see.
Drakosha
Great, thanks to all!
Slava N
I get answer when the vector is ´vector<MyClass>´. But I am seeing the same behavior ( 1 ctor and 2 dtor ) when i have a ´vector<sharedptr<MyClass>>´. Why is that? Does a copy of MyClass gets created even if i have a vector of sharedptrs?sharedprt<> is ref-counted smart ptr.
Gautam Borad
@Gautam - you would need to show some code for me to discuss what might be happening intelligently. It sounds like maybe you have a local/automatic variable (like `b` in the question), and are pushing a `shared_ptr<>` to it onto the vector. In that case you have a bug that 2 entities think they own the object and are responsible for destroying it. One is the 'scope' which owns local/automatic variables, and the other is the `shared_ptr`. You cannot pass ownership of a local/automatic to a `shared_ptr` (not entirely true, see the next comment).
Michael Burr
You could create a `shared_ptr` to a local/automatic and prevent double-destruction by creating it with a 'null deleter' (http://www.boost.org/doc/libs/1_34_0/libs/smart_ptr/sp_techniques.html#static). But if you do that you have to take care that the lifetime of the `shared_ptr` to that object not extend beyond the lifetime of the automatic variable.
Michael Burr
+3  A: 

If you want to log all copies and constructions you should add an explicit copy constructor so that the compiler doesn't create one for you.

MyClass( const MyClass& )
{
    cout << "Copy constructor\n";
}

You can, in your copy constructor, call your assignment operator. It is a reasonably common way of implementing things, however with your definition of operator=, this may have serious problems.

You have an unconventional implementation of operator=. operator= should return a reference to the class on which it is called (to enable proper chaining), but you return a new class instance by value. This means that if you tried to call operator= from your copy constructor you may well end up with infinite recursion. Try this operator= instead:

MyClass& operator=( const MyClass& )
{
    cout << "operator=\n";
    return *this;
}

When defining an assignment operator you should always consider the possibility that the parameter and *this may refer to the same object and ensure that the definition of the operator won't have any unintended effects in this scenario.

Charles Bailey
+1, also, you should mention that a self-assignment check should be made at the beginning of the operator= method.
Nick D
Both great tips! Wish I could mark this as accepted answer too. Thanks!
Slava N
This assignment operator is safe for self-assignment so there is no need to perform it. Many assignment operators are safe for self-assignment (e.g. those implemented with copy and swap), those that require it are often not exception safe (see Herb Stutter's excellent Exceptional C++). For these reasons, I prefer to add the check only when necessary.
Charles Bailey
Ok, I said you should because it's a good practice (ie future modification of the method). That's all.
Nick D
Possibly, but better practice (IMHO) would be to maintain the assignment operator's inherent exception safety and self-assignment safety in future modifications. I would consider needing to add an explicit check something of a compromise.
Charles Bailey
@Nick: The copy/swap idiom does not require a test for self, and test for self is now considered bad practice as its existence indicates you are not using copy/swap. Copy and swap is the preferred technique because it provides the "Strong Exception Guarantee". Note. I use copy and swap and test for self (but not because assignment to self is bad. But because it is an optimization technique. Saying that it is not required though).
Martin York
@Martin York: Do you have a (brief) example of where checking for a self-assignment as an optimization made a positive difference? I understand the rationale but I've never actually had a situation like that myself. If I did I would be tempted to look at the algorithm or process that was causing multiple self-assignments and see if there was an improvement or alternative that would help at that level, rather than in the class being operated on.
Charles Bailey
A: 

Try this code:

#include <iostream>
#include <vector>

class MyClass
{
public:
    MyClass(void)
    {
     std::cout << "constructor(void)" << std::endl;
    };

    MyClass(const MyClass& rhs)
    { 
     std::cout << "constructor(copy)" << std::endl;
    };

    MyClass& operator=(const MyClass& rhs)
    {
            // check for self-assignment. saying a = a should have
            // no effect.
            if (this != &rhs)
            {
             std::cout << "operator=" << std::endl;
            }

     // note the return type. now return a reference to self
     // to allow chaining: a = b = c = d;
     // This is the standard way of implementing
     // operator=, but wasn't related to your question too much.
     // Just good to clarify on.
     return *this; 
    };

    ~MyClass(void)
    {
     std::cout << "destructor" << std::endl;
    };
};

int main(void)
{
    std::cout << "1" << std::endl;

    std::vector<MyClass> theArray;

    std::cout << "2" << std::endl;

    MyClass foo;

    std::cout << "3" << std::endl;

    theArray.push_back(foo);

    std::cout << "4" << std::endl;
}

And you will get:

  • 1
  • 2
  • constructor(void)
  • 3
  • constructor(copy)
  • 4
  • destructor
  • destructor

Copy-construct versus operator=

The reason it is using the copy-constructor instead of operator= is because the object does not exist until the push_back.

That is, it's like saying this:

MyClass a;
MyClass b(a); // push_back

Rather than:

MyClass a;
MyClass b;
b = a; // push_back

The reason the first is used then is because whe you push_back, the object is created right at that call site inside the vector.

It would be silly for the vector to always call both the constructor(void), then operator=, rather than constructing directly from the original with one constructor(copy).

To further this point, something like:

MyClass a;
MyClass b = a;

With automatically be treated like this by the compiler:

MyClass a;
MyClass b(a);

So the standard itself (I don't know the exact quote, I know Neil Butterworth's does) says that this conversion should occur, because it's more correct.

Destructors

The reason there are two destructor's is that the vector has its destructor called, upon which is destructs all of it's contents. in this case, this is one instance of the class, and that accounts for one output of "destructor". Had you pushed n MyClass's on the vector, you'd have n calls to the destructor, one for each.

The second comes from destructing b.

GMan