push_back destroying previous entry

Started by
8 comments, last by Zahlman 14 years, 8 months ago
Please see the code here : codepad link On line 6 of the output we get "Destructor of Foo Bob of index 0" That means pushing back the second Foo destroys the previous one ? I don't understand what happens here.
Advertisement
The vector is probably resizing itself so it can fit the second instance. When writing classes in C++, you need to follow the rule of three. In this case, since you've defined a destructor, you should also probably define a copy constructor and operator =.
Quote:Original post by aarbron
Please see the code here : codepad link
On line 6 of the output we get
"Destructor of Foo Bob of index 0"
That means pushing back the second Foo destroys the previous one ? I don't understand what happens here.
I only glanced at the code, but I'm guessing it's due to reallocation.

std::vector may reallocate its storage in certain circumstances, which can result in object destructors being invoked. (There are ways to gain some control over this behavior, if there's a need to do so.)
Quote:When writing classes in C++, you need to follow the rule of three. In this case, since you've defined a destructor, you should also probably define a copy constructor and operator =.
@The OP: Make sure that you understand the 'rule of three' before applying it (in other words, don't apply it blindly). In your posted code, for example, there's really no reason to define a copy constructor or copy assignment operator (since the destructor itself is only there for illustrative purposes, and doesn't actually perform any manual resource management).
Quote:Original post by jyk
@The OP: Make sure that you understand the 'rule of three' before applying it (in other words, don't apply it blindly). In your posted code, for example, there's really no reason to define a copy constructor or copy assignment operator (since the destructor itself is only there for illustrative purposes, and doesn't actually perform any manual resource management).
Well, there's technically no reason to define a destructor, either :-) If the point of the code is just to print out what's happening, a copy constructor will certainly make things clearer.

But I agree that you should understand the rule before applying it blindly (that is always implied, IMO. C++ is not the place for cargo-cult programming!)
Quote:Well, there's technically no reason to define a destructor, either :-)
Yeah, that's what I meant by 'the destructor itself is only there for illustrative purposes' :)
A vector has a capacity and a size, the former is the number of elements for which the vector has allocated memory, whilst the latter is the number of constructed elements presently held. A vector's capacity is full when the two are equal:

bool is_full = foos.size() == foos.capacity();

In this case there is no more free space for any more elements to be pushed-back, attempting to add a new element will force the vector to create a new, larger, range of memory; copy the elements across and destroy the old range.

Your phantom destructor invocation is actually due to the destruction of the old range when the vector is resizing itself. If you write a custom copy-constructor you would similarly be able to see the copying of the elements too.
Makes sense now, thanks. I'll just go with a vector<Foo*> in my actual code because it's a non POD and the copy would only add complexity.
Quote:Original post by aarbron
Makes sense now, thanks. I'll just go with a vector<Foo*> in my actual code because it's a non POD and the copy would only add complexity.
Which sort of complexity do you mean? Asymptotic?
Being a non-POD type alone doesn't usually turn storing instances by-value into a bad thing.

Typically a std::vector of raw pointers is considered somewhat of a faux pas in modern C++; the reason, coming back to complexity, is that dumb, raw pointers typically increase design complexity and maintenance complexity whilst bringing to the table only nil to nominal performance improvements. The usual alternative candidates, in no particular order, are these:

a) std::vector<Foo> - by value, use std::vector<>::reserve() to reserve capacity ahead of time.
b) std::deque<Foo> - doesn't require reallocations.
c) std::vector< boost::shared_ptr<Foo> > - a vector of smart pointers.
d) boost::ptr_vector<Foo> - a vector of pointers internally.

Frequently one of those does the same job only better. [smile]
Quote:Original post by aarbron
Makes sense now, thanks. I'll just go with a vector<Foo*> in my actual code because it's a non POD and the copy would only add complexity.


1) The type you demonstrate is not a POD, but it doesn't need a copy constructor anyway. The requirements are looser than that: as long as the default copy-constructor makes sense, you can use it. The way the default copy-constructor works is to call copy-constructors recursively for each base and each data member.

If your class contains things that make this not work, the proper solution to your headaches is probably to change that, rather than working around it. In particular, if you have data members that are raw pointers, consider replacing them with an appropriate kind of smart pointer.

2) Using a vector of pointer-to-T instead of a vector of T also adds complexity. Quite a bit, in fact, because you assume responsibility for memory management. Again, this is what smart pointers are for. (Or, in some cases, indirect containers such as boost::ptr_vector.)

This topic is closed to new replies.

Advertisement