views:

221

answers:

4

I have made the following little Program: (basically a class that couts if it gets created, copied or destroyed and a main that does some of that)

class Foo
{
public:
 Foo(string name): _name(name)
 {
  cout << "Instance " << _name << " of Foo created!" << std::endl;
 };
 Foo(const Foo& other): _name(other._name)
 {
  cout << "Instance " << _name << " of Foo copied!" << std::endl;
 };

 ~Foo()
 {
  cout << "Instance " << _name << " of Foo destroyed!" << std::endl;
 }
 string _name;
};



int main( int argc, char**argv)
{
 Foo albert("Albert");
 Foo bert("Bert");
 {
  vector<Foo> v1, v2;
  system("PAUSE");  

  v1.push_back(albert);
  system("PAUSE");

  v2.push_back(bert);
  system("PAUSE");

  v1 = v2;
  system("PAUSE");  
 }
  system("PAUSE");
}

The output looks like this:

Instance Albert of class Foo created!
Instance Bert of class Foo created!
Press any key...
Instance Albert of class Foo copied!    
Instance Albert of class Foo copied!     // why another copy?
Instance Albert of class Foo destroyed!  // and destruction?
Press any key...
Instance Bert of class Foo copied!
Instance Bert of class Foo copied!
Instance Bert of class Foo destroyed!
Press any key...                      // v1=v2 why did the albert instance not get destroyed?
Press any key...                       
Instance Bert of class A destroyed!
Instance Bert of class A destroyed!
Press any key...                       // there's still an albert living in the void

This strikes me as very odd. Why do I even bother passing something as a reference if it gets copied twice anyway? Why does the v1.operator=(other) not destroy the elements it contains? It would fit nicely with the behaviour of shared_ptr. Can someone tell me why?

ADDITION I put this in an endless loop and checked the mem usage, it doesn't seem to produce a mem leak at least.

ADDITION Ok, the mem is not an issue because it uses operator= rather than the copy ctor, ok thanks. When I add

v1.reserve(10);
v2.reserve(10);

the logical number of copies takes place. without that it reallocates and copies the whole vector for every single push_back, (which I find quite retarded even for small vectors). Looking at this I will consider using .reserve more and optimize my assignment operators Like hell :)

ADDITION: SUMMARY

  1. All these issues seem specific to VC++2005.
  2. If the size of the two containers match, my implementation uses operator= on the elements instead of destroying the old ones and copying the new ones, which seems sound practice. IF the sizes differ, normal destruction and copying are used.
  3. With the 2005 Implementation, one has to use reserve! Otherwise abysmal and not std compliant performance.
  4. These black boxes are much blacker than I thought.
+3  A: 

There is an auto generated = operator. When you do v1 = v2 that operator is used. At that point one of the "albert" instances becomes "bert". Try adding this function to Foo:

Foo& operator = (const Foo& rval) {
    cout << _name << " = " << rval._name << endl;
    _name = rval._name;
    return *this;
}

This is the same as the auto generated but prints out a debug message so you can see what is happening.

stribika
well and what about the surplus copies and the mem leak (which is actually not happening, see above)
AndreasT
If you add this function (or a static counter) you can see that there is no leak. As for the extra copies there is no guarantee that a STL container will only copy things once. It may be a pass by value or some other thing depending on your STL implementation.
stribika
+4  A: 

Why do I even bother passing something as a reference if it gets copied twice anyway?

You should consider STL container types as blackbox that can copy the objects you store as often as they need to. For instance, every time the container is resized, all of the objects will be copied.

It is possible that your compiler's implementation of push_back() uses a temporary extra copy. On my machine (gcc on Mac OS X), there are no extra copies during push_back() (according to your program's output).

This copy happens somewhere in the STL code, not in your copy constructor (since it uses a reference).

Why does the v1.operator=(other) not destroy the elements it contains?

Foo::operator= will be called for the "albert" instance with the "bert" instance as argument. Therefore, there is no implicit destroy and copy operation here. You might want to verify this by providing your own implementation for the operator:

Foo& operator=(const Foo& other) {
    cout << "Instance " << other._name << " of Foo assigned to " << _name << "!" << std::endl;
    return *this;
}

This produces the following output on my machine:

Instance Albert of Foo created!
Instance Bert of Foo created!
Instance Albert of Foo copied!
Instance Bert of Foo copied!
Instance Bert of Foo assigned to Albert!
Instance Bert of Foo destroyed!
Instance Albert of Foo destroyed!
Instance Bert of Foo destroyed!
Instance Albert of Foo destroyed!

Ferdinand Beyer
And keep in mind that just keeping track of copies means your copy ctor becomes more complex to the point where the optimizer may no longer eliminate it. So you're counting ctor calls that exist only because they're counted!
MSalters
The black box argument is valid, however, I always hoped these were not stupid black boxes :) . <subjective!>Well yet another M$ disappointment.</subjective>
AndreasT
@MSalters: This is interesting. The C++ uncertainty principle.
stribika
Ferdinand Beyer
Expensive instanciation or not, reallocation in itself is expensive.Well I know about const refs, it just doesn't matter in the example, so I got lazy ;-)
AndreasT
+1  A: 

The "double-copying" does not happen when compiled with GCC. This must be specific to the way std::vector is implemented in VC++.

Vijay Mathew
It seems specific to VS2005
AndreasT
+1  A: 

Visual Studio 2008 gives me the following output:

Instance Albert of Foo created!
Instance Bert of Foo created!
Press any key to continue . . .
Instance Albert of Foo copied!
Press any key to continue . . .
Instance Bert of Foo copied!
Press any key to continue . . .
Press any key to continue . . .  << here auto-generated operator= doing its job
Instance Bert of Foo destroyed!
Instance Bert of Foo destroyed!  << this is Albert was originally 
Press any key to continue . . .

Seems to be that std::vector implementation isn't very effective in VS2005.

Kirill V. Lyadvinsky
Thats most definately true!
AndreasT