Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Unexpected result when a std::vector goes out of scope

25 views
Skip to first unread message

Joseph Hesse

unread,
Mar 15, 2017, 9:57:28 AM3/15/17
to
/*
I wrote a test program where I insert 2 objects into a vector. The
objects have a contrived copy constructor and their destructors print a
message.
When the program ends I expect to see 4 destructor calls, 2 from the
death of the objects and 2 when the vector dies.
An extra object appears and I don't know where it came from.
Thank you,
Joe
*/
///////////////////////////////////////////////////////
#include <iostream>
#include <vector>
using namespace std;

class X
{
private:
int x;
public:
X(int u = 0) : x(u) {}
~X() { cout << "Bye from X with x = " << x << endl; }
X(const X & rhs) : x(10*rhs.x) {}
};

int main()
{
X xobj1(1), xobj2(2);

vector<X> vec;

vec.push_back(xobj1);
vec.push_back(xobj2);

return(0);
}
///////////////////////////////////////////////////////
$ g++ --std=c++11 Test.cpp
$ ./a.out
Bye from X with x = 10
Bye from X with x = 100 <== Where did this come from?
Bye from X with x = 20
Bye from X with x = 2
Bye from X with x = 1

guy.tr...@gmail.com

unread,
Mar 15, 2017, 10:43:38 AM3/15/17
to
On Wednesday, March 15, 2017 at 1:57:28 PM UTC, Joseph Hesse wrote:
> Bye from X with x = 100 <== Where did this come from?

When you push_back xobj2, vec is resized and the first copy of xobj1
is copied to the newly allocated memory. The old copy (x=10) is destroyed
during the push_back. You then see the destructor calls you expected, except
because the first element of vec has been copied twice, you see x=100.

If you call vec.reserve(2) before the push_back calls, you should see what
you were expecting.

Alf P. Steinbach

unread,
Mar 15, 2017, 10:57:48 AM3/15/17
to
When I add some tracing output to your `main`,


int main()
{
X xobj1(1), xobj2(2);

vector<X> vec;

cout << "Capacity " << vec.capacity() << " before adding o1\n";
vec.push_back(xobj1);
cout << "Capacity " << vec.capacity() << " before adding o2\n";
vec.push_back(xobj2);
cout << "Capacity " << vec.capacity() << " after adding o2\n";
}


then with both g++ and Visual C++ I get this output:


Capacity 0 before adding o1
Capacity 1 before adding o2
Bye from X with x = 10
Capacity 2 after adding o2
Bye from X with x = 100
Bye from X with x = 20
Bye from X with x = 2
Bye from X with x = 1


Here you can see that that the vector increased its buffer size, by
replacing its buffer, both for the first insertion and for the second
insertion. In the last buffer replacement the copy of the o1 object was
copied a second time, invoking the 10*rhs.x in the copy constructor, a
second time.

I believe the standard doesn't specify the initial capacity of a vector,
and anyway it for sure doesn't specify the capacity after adding an
item, so the behavior here is compiler-specific. You cannot rely on any
particular buffer management strategy except the general complexity
guarantees, which imply a geometric increase of capacity.

You can however guaranteed avoid the buffer replacements during the
insertion sequence by calling `.reserve(a_suitable_initial_capacity)`.
Since this avoids multiple dynamic allocations, which are notoriously
slow, it's often done as a matter of course. I guess if `std::vector`
were designed today, with what we now know of usage patterns, it would
have had constructors that supported specification of an initial
capacity, and it might possibly have provided access to uninitialized
parts of the buffer for POD item type, for use as API function result.


Cheers & hth.,

- Alf

Joseph Hesse

unread,
Mar 15, 2017, 3:04:57 PM3/15/17
to
On 03/15/2017 08:57 AM, Joseph Hesse wrote:
> #include <iostream>
> #include <vector>
> using namespace std;
>
> class X
> {
> private:
> int x;
> public:
> X(int u = 0) : x(u) {}
> ~X() { cout << "Bye from X with x = " << x << endl; }
> X(const X & rhs) : x(10*rhs.x) {}
> };
>
> int main()
> {
> X xobj1(1), xobj2(2);
>
> vector<X> vec;
>
> vec.push_back(xobj1);
> vec.push_back(xobj2);
>
> return(0);
> }

Thank you for the replies.

I added a Move Constructor to Class X, thinking that vector<X> would use
it when moving things around after space reallocation.
X(X && rhs) : x(rhs.x) {}

Same result.

I learned something from this example. "Don't put side effects into Copy
Constructors."

Paavo Helde

unread,
Mar 15, 2017, 4:22:09 PM3/15/17
to
On 15.03.2017 21:04, Joseph Hesse wrote:
> X(X && rhs) : x(rhs.x) {}

You need to make this

X(X && rhs) noexcept : x(rhs.x) {}

as explained by Scott in another thread.
0 new messages