Avoid redundant construction/destruction of vector elements

I am wondering if I'm doing it right, with regard to which constructors I need to define to prevent redundant constructions/destructions of objects.
Obviously, it's trivial for an int as the only member variable, but that's just for example.

In the following code,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <vector>

class Base {
public:   
    Base(int id) : id(id) { }
    int id;
};

class Derived : public Base {
public:
    Derived(int id)
    : Base(id) { }
    
    Derived(const Derived& derived)
    : Base(derived.id) { }
    
    Derived(Derived&& derived)
    : Base(derived.id) { }
    
    ~Derived()
    {
        std::cout << "~Derived() " << id << "\n";   
    }
};

void process(const std::vector<Derived>&)
{
    // ...
}

int main()
{
    std::vector<Derived> deriveds = {
        Derived(42),
        Derived(43),
        Derived(44)
    };
       
    process(deriveds);
}

the output is:
~Derived() 44
~Derived() 43
~Derived() 42
~Derived() 44
~Derived() 43
~Derived() 42


Clearly, it's constructing & destructing each element of the vector as a temporary. How can I avoid that?

Edit:
If I attempt to use emplace_back, the behavior is even crazier:
1
2
3
4
    std::vector<Derived> deriveds;
    deriveds.emplace_back(1);
    deriveds.emplace_back(2);
    deriveds.emplace_back(3);

~Derived() 1
~Derived() 2
~Derived() 1
~Derived() 3
~Derived() 2
~Derived() 1

Where did a third (1) come from?? (I know, the moved object should be considered invalid. Point still is it's creating 3 temporaries that I don't want.)

Edit 2:
Only potential fix I can think of would be to add another layer of indirection i.e. making it a vector<pointer>.
Last edited on
emplace_back() is resizing the vector which is why you get extra ~Derived() called as part of the resizing. To avoid the resizing use .reserve() to allocate memory for the specified number of objects. Note that where a type is used that uses allocated memory then you'd use std::move() with && and also & and && constructors for Base. Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <vector>
#include <utility>

class Base {
public:
	Base(int id) : id(id) {
		std::cout << "Base " << id << "\n";
	}

	int id;
};

class Derived : public Base {
public:
	Derived(int id)
		: Base(id) {
		std::cout << "derived int " << id << "\n";
	}

	Derived(const Derived& derived)
		: Base(derived.id) {
		std::cout << "derived & " << derived.id << '\n';
	}

	Derived(Derived&& derived) noexcept
		: Base(derived.id) {
		std::cout << "derived && " << derived.id << '\n';
	}

	~Derived() {
		std::cout << "~Derived() " << id << "\n";
	}
};

void process(const std::vector<Derived>&) {
	// ...
}

int main() {
	std::vector<Derived> deriveds;
	deriveds.reserve(3);

	deriveds.emplace_back(1);
	deriveds.emplace_back(2);
	deriveds.emplace_back(3);

	process(deriveds);
}



Which displays:
Base 1
derived int 1
Base 2
derived int 2
Base 3
derived int 3
~Derived() 1
~Derived() 2
~Derived() 3

Last edited on
Riiight, I totally forgot that the vector would need to resize. Thanks.

Still, I'm kinda bummed that I even need to "manually" call reserve and emplace_back. I would prefer if there was a way to make the original code work w/o creating extra copies:

1
2
3
4
5
std::vector<Derived> deriveds = {
        Derived(42),
        Derived(43),
        Derived(44)
    };


Edit: Had a brain blast. The problem really is with the dynamic nature of vector, as you pointed out with the resize nonsense.

So, if I use a std::array instead, that lets me initialize everything up-front:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <array>

class Base {
public:   
    Base(int id) : id(id) { }
    int id;
};

class Derived : public Base {
public:
    Derived(int id)
    : Base(id) { }
    
    Derived(const Derived& derived)
    : Base(derived.id) { }
    
    Derived(Derived&& derived)
    : Base(derived.id) { }
    
    ~Derived()
    {
        std::cout << "~Derived() " << id << "\n";   
    }
};

void process(const std::array<Derived, 3>&)
{
    // ...
}

int main()
{
    std::array<Derived, 3> deriveds = {
        Derived(42),
        Derived(43),
        Derived(44)
    };
       
    process(deriveds);
}

~Derived() 44
~Derived() 43
~Derived() 42
Last edited on
Yep - if you know at compile time how many elements there will be.
Or use the reserve function in vector.
1
2
3
4
5
6
std::vector <Derived> deriveds;
deriveds.reserve(3);
deriveds.push_back(Derived(42));
deriveds.push_back(Derived(43));
deriveds.push_back(Derived(44));
//etc. 


But this does throw out the option of an Initialization List.
C++ 11 might have an option to load from std::initializer_list, but that's more work than I usually feel like putting in.
Last edited on
In this case, since Derived has a non-explicit constructor taking just one int, you're allowed to write

std::vector<Derived> deriveds = { 42, 43, 44 };

I was messing around with this late yesterday, and found that GCC alone calls the iterator-based constructor, #5 in cppreference, to construct the vector from a pair of const int*. As such it doesn't create any extra derived objects and doesn't need to reallocate anything.
https://en.cppreference.com/w/cpp/container/vector/vector.html

I found this surprising. I thought the compiler had to make an initializer_list<Derived> from the { 42, 43, 44 } and then copy the contents of the initializer_list into the vector. Clang + MSVC seem to behave this way.

I wonder if the different behavior is due to the library, or due to the compiler.
Registered users can post here. Sign in or register to post.