

Vectors and unique pointers
source link: https://www.sandordargo.com/blog/2023/04/12/vector-of-unique-pointers
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Vectors and unique pointers
In this post, I want to share some struggles I had twice during the last few months. For one of my examples, I wanted to initialize a std::vector
with std::unique_ptr
. It didn’t compile and I had little time, I didn’t even think about it. I waved my hand and changed my example.
Then I ran into the same issue at work, while we were pairing over an issue. I couldn’t just wave anymore, and luckily we also had the time to go a bit deeper.
What we wanted was a little bit different and more complex though. We wanted to return a vector of unique pointers to a struct that holds - among others - a unique pointer.
This is a simplified version of what we wanted:
class Resource {
// unimportant
};
struct Wrapper {
std::string name;
std::unique_ptr<Resource> resource;
}
// somewhere in a function
std::vector<std::unique_ptr<Wrapper>> v{
std::make_unique<Wrapper>(std::move(aName), std::make_unique<Resource>()),
std::make_unique<Wrapper>(std::move(anotherName), std::make_unique<Resource>())
};
That’s a bit too complex to start with, so let’s dissect the issue into two parts.
No compiler-generated copy constructor
Let’s first omit the external unique pointer and try to brace-initialize a vector
of Wrapper
objects.
#include <memory>
#include <string>
#include <vector>
class Resource {
// unimportant
};
struct Wrapper {
std::string m_name;
std::unique_ptr<Resource> m_resource;
};
int main() {
std::string aName = "bla";
std::vector<Wrapper> v{Wrapper{aName, std::make_unique<Resource>()}};
}
The first part of the problem is that we cannot {}
-initialize this vector
of Wrapper
s. Even though it seems alright at a first glance. Wrapper
is a struct
with public members and no explicitly defined special functions. Our {}
-initialization follows the right syntax and the parameters are passed in the right order.
Still, the compiler says stop! Here is a part of the error messages. As vector
is a template we cannot expect a concise message.
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/stl_uninitialized.h:90:56: error: static assertion failed: result type must be constructible from input type
90 | static_assert(is_constructible<_ValueType, _Tp>::value,
If we scroll up in the errors we’ll find a more explanatory line:
In instantiation of 'constexpr bool std::__check_constructible() [with _ValueType = Wrapper; _Tp = const Wrapper&]':
So the problem is Wrapper
cannot be constructed from const Wrapper&
, in other words, Wrapper
cannot be copy constructed. That makes sense! It has a move-only member, std::unique_ptr<Resource> m_resource
! Because of this move-only member, the compiler cannot automatically generate a copy constructor.
A std::vector
always copies its std::initializer_list
That’s all fine but why do we need a copy constructor? Why cannot we benefit from move semantics?
We can spot the answer on C++ Reference! std::vector
has only one constructor involving a std::initializer_list
and there the initializer_list
is taken by value. In other words, vector
copies its initializer_list
. Always.
As the passed in initializer_list
is going to be copied, the contained type must be copy-constructible. That’s clearly not the case for Wrapper
which we can easily assert.
struct Wrapper {
std::string m_name;
std::unique_ptr<Resource> m_resource;
};
static_assert(std::is_copy_constructible<Wrapper>());
/*
main.cpp:18:20: error: static assertion failed
18 | static_assert(std::is_copy_constructible<Wrapper>());
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
Let’s make contained types copy constructible
That’s quite easy to fix, we need to provide a user-defined copy constructor, such as Wrapper(const Wrapper& other): m_name(other.m_name), m_resource(std::make_unique<Resource>()) {}
. At the same time, let’s not forget about the rules of 0/3/5, so we should provide all the special functions.
Now we don’t have the problem anymore, we can easily use the brace initialization.
#include <memory>
#include <string>
#include <vector>
class Resource {
// unimportant
};
struct Wrapper {
std::string m_name;
std::unique_ptr<Resource> m_resource;
Wrapper() = default;
~Wrapper() = default;
Wrapper(std::string name, std::unique_ptr<Resource> resource) : m_name(std::move(name)), m_resource(std::move(resource)) {}
Wrapper(const Wrapper& other) : m_name(other.m_name), m_resource(std::make_unique<Resource>()) {}
Wrapper(Wrapper&& other) = default;
Wrapper& operator=(const Wrapper& other) {
m_name = other.m_name;
m_resource = std::make_unique<Resource>();
return *this;
}
Wrapper& operator=(Wrapper&& other) = default;
};
int main() {
std::string aName = "bla";
std::vector<Wrapper> v{Wrapper{aName,
std::make_unique<Resource>()}};
}
But what if we cannot or do not want to modify Wrapper
? Do we have any other options?
Of course!
We can also extract vector
initialization
If we have to avoid the brace-initialization of the vector, we can simply default initialize it, probably reserving then enough space for the items we want to add, and use either vector
’s emplace_back()
or push_back()
methods.
There is not a big difference in this case between emplace_back()
and push_back()
. push_back()
will call the appropriate constructor first and then the move constructor. emplace_back()
will only make one constructor call. At least that’s the theory. Let’s see if we’re right. I amended Wrapper
so that each of its special functions prints.
#include <memory>
#include <string>
#include <vector>
#include <iostream>
class Resource {
// unimportant
};
struct Wrapper {
std::string m_name;
std::unique_ptr<Resource> m_resource;
Wrapper() {
std::cout << "Wrapper()\n";
}
~Wrapper() {
std::cout << "~Wrapper()\n";
}
Wrapper (std::string name, std::unique_ptr<Resource> resource) : m_name(std::move(name)), m_resource(std::move(resource)) {
std::cout << "Wrapper (std::string name, std::unique_ptr<Resource> resource)\n";
}
Wrapper(const Wrapper& other) : m_name(other.m_name), m_resource(std::make_unique<Resource>()) {
std::cout << "Wrapper (const Wrapper& other)\n";
}
Wrapper(Wrapper&& other) {
std::cout << "Wrapper (Wrapper&& other)\n";
}
Wrapper& operator=(const Wrapper& other) {
std::cout << "Wrapper& operator=(const Wrapper& other)\n";
return *this;
}
Wrapper& operator=(Wrapper&& other) {
std::cout << "Wrapper& operator=(Wrapper&& other)\n";
return *this;
}
};
int main() {
std::string aName = "bla";
std::vector<Wrapper> v{};
v.emplace_back(aName, std::make_unique<Resource>());
// v.push_back(Wrapper(aName, std::make_unique<Resource>()));
}
/*
output with emplace_back:
Wrapper (std::string name, std::unique_ptr<Resource> resource)
~Wrapper()
*/
/*
output with push_back
Wrapper (std::string name, std::unique_ptr<Resource> resource)
Wrapper (Wrapper&& other)
~Wrapper()
~Wrapper()
*/
Our assumption was right, except that I forgot to note the extra destructor call on a moved-from object. While these extra calls would be often negligible, we have no reason not to use emplace_back()
. It’s not more difficult to write it and it’s better for the performance.
With that, we’ve seen why we couldn’t {}
-initialize a vector
of Wrapper
s following the rule of zero, where Wrapper
is a type that allocates on the heap.
Now let’s move over to the other issue.
The same issue with initializer_list
once again
Actually, by answering the first question, we also learned why we cannot {}
-initialize a vector
of unique_ptr
s. It’s the problem of an initializer_list
taken by value instead of reference and the fact that unique_ptr
s cannot be copied.
Can we do anything to improve the situation?
We can extract the creation and population of the vector
to a separate method so that we don’t have the visual noise of vector
insertions along with the rest of the code.
std::vector<std::unique_ptr<Resource>> createResources() {
std::vector<std::unique_ptr<Resource>> vec;
vec.push_back(std::make_unique<Resource>());
vec.push_back(std::make_unique<Resource>());
vec.push_back(std::make_unique<Resource>());
return vec;
}
int main() {
std::vector<std::unique_ptr<Resource>> resources = createResources();
// ...
}
But can we do something better?
We can make a better, generalized function that makes us a vector
of unique_ptr
s, but the idea behind is essentially the same: the pointers are added one by one after the construction of the vector.
Let me borrow an implementation by Bartek. I take this piece of code from C++ Storties and I encourage you to read the whole article on initializer_list
:
template<typename T, typename... Args>
auto makeVector(Args&&... args) {
std::vector<T> vec;
vec.reserve(sizeof...(Args));
(vec.emplace_back(std::forward<Args>(args)), ...);
return vec;
}
Now we can update our original example.
#include <memory>
#include <string>
#include <vector>
#include <iostream>
class Resource {
// unimportant
};
struct Wrapper {
std::string m_name;
std::unique_ptr<Resource> m_resource;
Wrapper() = default;
~Wrapper() = default;
Wrapper(std::string name, std::unique_ptr<Resource> resource) : m_name(std::move(name)), m_resource(std::move(resource)) {}
Wrapper(const Wrapper& other) : m_name(other.m_name), m_resource(std::make_unique<Resource>()) {}
Wrapper(Wrapper&& other) = default;
Wrapper& operator=(const Wrapper& other) {
m_name = other.m_name;
m_resource = std::make_unique<Resource>();
return *this;
}
Wrapper& operator=(Wrapper&& other) = default;
};
// this is from Bartek: https://www.cppstories.com/2023/initializer_list_improvements/
template<typename T, typename... Args>
auto makeVector(Args&&... args) {
std::vector<T> vec;
vec.reserve(sizeof...(Args));
(vec.emplace_back(std::forward<Args>(args)), ...);
return vec;
}
int main() {
[[maybe_unused]] std::vector<std::unique_ptr<Wrapper>> v = makeVector<std::unique_ptr<Wrapper>>(
std::make_unique<Wrapper>("keyboard", std::make_unique<Resource>()),
std::make_unique<Wrapper>("mouse", std::make_unique<Resource>()),
std::make_unique<Wrapper>("screen", std::make_unique<Resource>())
);
return 0;
}
Conclusion
Today, I shared with you a problem I faced lately. I wanted to {}
-initialize a vector
of unique pointers, but it didn’t work. A std::vector
takes an initializer_list
by value, so it makes a copy of it. Hence, the compilation will fail if you try to use an initializer_list
with move-only types.
If you want to use the {}
-initializer for a vector, you need to implement the move constructor. If that’s not an option and you want to separate the creation of the vector
, you have no other option than move the related code to a separate function.
Connect deeper
If you liked this article, please
- hit on the like button,
- subscribe to my newsletter
- and let’s connect on Twitter!
Recommend
-
38
README.md Word2Bits - Quantized Word Vectors Word2Bits extends the Word2Vec algorithm to output high quality quantized word vectors that take 8x-16x less storage/memory than regular word vector...
-
264
README.md Chinese Word Vectors 中文词向量 This project provides 100+ Chinese Word Vectors (embeddings) trained with different representations (dense and sparse), contex...
-
68
README.md LMDB Embeddings Query word vectors (embeddings) very quickly with very little querying time overhead and far less memory usage than gensim or...
-
50
There is a science joke that goes, “Geology rocks, but Geography is where it’s at.” At Elastic, we understand that location matters. That’s why we created theElastic Maps Service. The Elastic Maps Service (EMS) provides...
-
6
Vectors, slices, and functions, oh my! May 14, 2012 I wanted to bring together the various ideas around vectors and function types into one post. The goals of these changes are to achieve orthogon...
-
10
Vectors, strings, and slices Apr 23, 2012 We’ve been discussing a lot about how to manage vectors and strings in Rust. Graydon sent out an excellent proposal which allows for a great number of use cases to be...
-
8
Using Vectors in ActionScript 3 and Flash Player 10 Tuesday, August 19, 2008 One of the new ActionScript features included in the Flash Player 10 Public Beta is...
-
23
Browse 300.000+ SVG Vectors and IconsExplore, search and find the best fitting icons or vectors for your projects using wide variety vector library. Download free SVG Vectors for commercial use.Easy To Search...
-
7
Resources Floral Backgrounds, Patterns, and Vectors for Your Designs ByBrooke Arnold PublishedFebruary 25, 2022February 25, 2022...
-
12
Column Major and Row Major Vectors and Matrices (for games) Dec 26, 2022 This is yet another post about understanding the difference between column major and row major...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK