Why does my class's destructor get called when I add instances to a vector?
Asked Answered
A

6

19

It seems that every time I add an object to the vector m_test, the destructor method is called. Am I missing something? How can I prevent this from happening?

class TEST
{
public:
    TEST();
    ~TEST();
    int * x;
};

TEST::TEST()
{
}

TEST::~TEST()
{
... it is called every time I push_back something to the vector ...
    delete x;
}

    vector<TEST> m_test;
    for (unsigned int i=0; i<5; i++)
    {
        m_test.push_back(TEST());
    }
Arson answered 17/2, 2012 at 16:5 Comment(1)
In C++11, you can use m_test.emplace_back() to avoid creating the temporary. In any case, always remember the Rule of Three.Collusive
H
18

The problem here is that you're violating the Rule of Three. Your class has a destructor so you need a copy-constructor and an assignment operator, too. Alternatively, you could not allow your class to be copied (for example by making T(T const&) and T& operator=(T const&) private, or by deriving from boost::noncopyable), and then resize the vector instead of using push_back.

In the first case, you can just push_back your class as you usually would. In the second, the syntax would be something like

std::vector<TEST> vec(5);
// vec now has five default-constructed elements of type TEST.

Not doing either of these things is a bad idea, as you are very likely to run into double deletion issues at some point -- even if you think you'll never copy or assign a TEST where x != nullptr, it's much safer to explicitly forbid it.

By the way, if you have member pointers that should be deleted when an object goes out of scope, consider using smart pointers like scoped_ptr, unique_ptr and shared_ptr (and maybe auto_ptr if you're unable to use Boost or C++11).

Heigho answered 17/2, 2012 at 17:9 Comment(2)
Thank you Anton, I am new to C++ programming, and never heard of Rule of Three. Thank you for pointing me to the right direction.Arson
Where did you learn this...? My previous CPP course totally sucks...Meredi
S
9

It's not called when you push_back, it's called when the temporary is destroyed.

To fix it in your example:

TEST test;
for (int i = 0; i < 5; ++i)
{
    m_test.push_back(test);
}

Should only call it once.

Your code is creating a temporary TEST within the loop, using it in push_back, then that temporary is going out of scope when the loop ends/repeats and getting destroyed. That occurs exactly as it should, since the temporary TEST needs cleaned up.

If you want to avoid that, you need to do anything else but make a temporary object for each push. One potential solution is to:

vector<TEST> m_test(5); // Note reserving space in the vector for 5 objects

std::fill(m_test.begin(), m_test.end(), TEST()); // Fill the vector with the default ctor

Depending on how your STL is optimized, this may not need to make multiple copies.

You may also be able to get better handling if you implement a copy constructor in your TEST class, like:

TEST::TEST(const TEST & other)
{
    x = new int(*other.x); // Not entirely safe, but the simplest copy ctor for this example.
}

Whether this is appropriate, or how you handle it, depends on your class and its needs, but you should typically have a copy constructor when you have defined your own regular constructor and destructor (otherwise the compiler will generate one, and in this case, it will result in copied and hanging pointers to x).

Succeed answered 17/2, 2012 at 16:8 Comment(0)
M
2

To avoid destruction of a temporary and to avoid copy constructors, consider using vector::resize or vector::emplace_back. Here's an example using emplace_back:

vector<TEST> m_test;
m_test.reserve(5); 
for ( uint i=0; i<5; i++ ) 
{
    m_test.emplace_back();
}

The vector element will be constructed in-place without the need to copy. When vt is destroyed, each vector element is automatically destroyed.

c++0x is required (use -std=c++0x with gnu). #include <vector> is of course also required.

If a default constructor is not used (for example, if the TEST::x was a reference instead of a pointer), simply add arguements to the call to emplace_back() as follows:

class TEST
{
public:
    TEST( int & arg) : x(arg) {;} // no default constructor
    int & x; // reference instead of a pointer.
};

. . . 

int someInt;

vector<TEST> m_test;
m_test.reserve(5);
for ( uint i=0; i<5; i++ ) {
    m_test.emplace_back( someInt ); // TEST constructor args added here.
}

The reserve() shown is optional but insures that sufficient space is available before beginning to construct vector elements.

Malave answered 12/5, 2013 at 3:21 Comment(0)
N
1

vector.push_back() copies the given object into its storage area. The temporary object you're constructing in the push_back() call is destroyed immediately after being copied, and that's what you're seeing. Some compilers may be able to optimize this copy away, but yours apparently can't.

Nadeen answered 17/2, 2012 at 16:9 Comment(0)
F
1

In m_test.push_back(TEST());, TEST() will create an temporary variable. After the vector copy it to its own memory, the temporary variable is destructed.

You may do like this:

vector<TEST> m_test(5, TEST());
Feature answered 17/2, 2012 at 16:9 Comment(0)
P
1

The destructor is not only being called for the temporary variable.

The destructor will also get called when the capacity of the vector changes.

This happens often on very small vectors, less so on large vectors.

This causes:

A new allocation of memory (size based on a growth metric, not just size+1)
Copy of the old elements into the new allocation
Destruction of the elements in the old vector
Freeing of the old vector memory.
Copy construction of the new item onto the end of the new new vector.

See the third answer here: Destructor is called when I push_back to the vector

Presumably answered 31/10, 2022 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.