Optimal creation new empty element in std::map - c++

For inserting new elements std::map requires presence std::pair object.
I didn't find an alternative method to add a new element into std::map without constructing such std::pair object.
But suppose the std::map value is a heavy object consuming a lot of resources. Inserting such element into std::map constraints one unnecessary copy. In the case
I have some object X needed to be copied directly into std::map, std authors constrain me to perform this copy in two steps:
copy X->std::pair
copy pair obj -> std::map insert location.
How to eliminate these unneeded copy? Implementing Move constructor doesn't solve my problem - the copy still exists.
I suspect some method in std::map which inserts a new empty element with some key but no value, without calling any constructor for the value. Then I can move the value to thereby my own way.
But I don't find such method in std::map!

the std::map value is a heavy object consuming a lot of resources. Inserting such element into std::map constraints one unnecessary
copy. How to eliminate these unneeded copy?
You are right about this and std::map::emplace is the only solution what we have available now. A small explanation about the use of std::map::emplace from cppreference.com is given below:
Careful use of emplace allows the new element to be constructed while
avoiding unnecessary copy or move operations. The constructor of the
new element (i.e. std::pair<const Key, T>) is called with exactly the
same arguments as supplied to emplace, forwarded via
std::forward<Args>(args)....
That means the a class-instances can be constructed in-place at the time of map insert(i.e, instead of constructing and copying), via std::map::emplace, when you provide a contractor in the class with exactly same arguments. And then, you need to use std::map::emplace along with std::piecewise_construct and std::forward_as_tuple(assuming that the class contains more than one member).
myMap.emplace(
std::piecewise_construct,
std::forward_as_tuple(/*key of map*/),
std::forward_as_tuple(/*all the members which you want to constrct in place*/)
);
To demonstrate the above case, I have made a small sample code in which the members of ClassA will be constructed in place without calling any of the special member functions. To make it sure, I have disabled default, copy and move constructors.
SEE LIVE HERE
#include <iostream>
#include <map>
#include <tuple>
class ClassA
{
int _val;
std::string _str;
public:
explicit ClassA(const int val, const std::string& str) : _val(val), _str(str)
{
std::cout << "A class: C'tor called...!\n";
}
// disable the following
ClassA() = delete;
ClassA(const ClassA&) = delete;
ClassA& operator=(const ClassA&) = delete;
ClassA(ClassA&&) = delete;
ClassA& operator=(ClassA&&) = delete;
};
class ClassB
{
ClassA _aObj;
public:
explicit ClassB(const int val, const std::string& str) : _aObj(val, str)
{
std::cout << "B class: C'tor called...!\n";
}
// disable the following
ClassB() = delete;
ClassB(const ClassB&) = delete;
ClassB& operator=(const ClassB&) = delete;
ClassB(ClassB&&) = delete;
ClassB& operator=(ClassB&&) = delete;
};
int main()
{
std::map<int, ClassB> myMap;
myMap.emplace(
std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(1, "some string")
);
return 0;
}
Output:
A class: C'tor called...!
B class: C'tor called...!
Update: On the other hand,
The element may be constructed even if
there already is an element with the key in the container, in which
case the newly constructed element will be destroyed immediately.
That means, your expectation(or assumption) of:
" I suspect some method in std::map which inserts a new empty element with some key but no value, without calling any constructor for the value. Then I can move the value to thereby my own way. "
is not possible to achieve, by above mentioned std::map::emplace way. As #aschepler pointed out in the comments, you can have maps with sometimes(optional) keys and no values by using the C++17 feature std::optional.
For that, you need to make the values as optional as follows and of course the compiler version support C++17 or later.
std::map<Key, std::optional<Value>> myMap;
Now you can construct an object anytime in the code and after a while, you can move it to appropriate key-value(i.e, entry). Last but not least, don't forget to provide default move-c'ntors
SEE SAMPLE CODE HERE
#include <optional>
class ClassA {
/* same as before*/
public:
// disable the copy c'tor
// enable move c'ntors
ClassA(ClassA&&) = default;
ClassA& operator=(ClassA&&) = default;
};
class ClassB {
/* same as before*/
public:
// disable the copy c'tor
// enable move c'ntors
ClassB(ClassB&&) = default;
ClassB& operator=(ClassB&&) = default;
};
int main() {
std::map<int, std::optional<ClassB>> myMap;
// created without calling any constructors
myMap.emplace(1, std::nullopt);
//later in the code
ClassB bObj{1, "JeJo"};
// again after..... move it to the required key-value
myMap[1] = std::move(bObj);
return 0;
}
Output:
A class: C'tor called...!
B class: C'tor called...!

You are looking for std::map::emplace
https://en.cppreference.com/w/cpp/container/map/emplace

Related

std::unordered_map gives error when inserting using emplace function

I'm getting this weird error when inserting an element into std::unordered_map using emplace function but not if I use operator[] function overload. This is my code:
#include <iostream>
#include <unordered_map>
#include <memory>
class B {
public:
B() = default;
~B() = default;
};
class A {
public:
A() = default;
~A() = default;
std::unique_ptr<B> b_ptr;
};
int main() {
std::unordered_map<std::string, A> mp;
// mp.emplace("abc", A()); // gives compiler here
auto& a = mp["def"];
}
I'm getting huge error print when compiled. This is a short note of error: template argument deduction/substitution failed
When you use emplace like mp.emplace("abc", A()); what you are doing is creating a temporary A, and that object is then copied/moved into the object that emplace is going to construct. When you did ~A() = default; in the class, that gets rid of the compiler supplied default move constructor, and the copy constructor is implicitly deleted because std::unique_ptr can't be copied so the A() can't be moved or copied into the object emplace is going to create.
You can fix this by using the std::piecewise_construct taged version of emplace to forward the parts of the key value pair to emplace like
mp.emplace(std::piecewise_construct, // call the piecewise_construct overload
std::forward_as_tuple("abc"), // forwards "abc" to the key
std::forward_as_tuple()); // forwards nothing to the value so it can be default constructed
or you could just add a move constructor to A using
A(A&&) = default;
so that emplace can move the A() you created in main into mp.
If you are using C++17 or later, replacing
// mp.emplace("abc", A());
with
mp.try_emplace("abc");
compiles fine.
The reason why mp.emplace("abc", A()); produces a compile error is that the sub-expression A() constructs a new object of type A in main()'s scope and so emplace() expects to directly forward this new object as the argument into some A constructor, but there is no such constructor (said differently, somewhere inside the emplace() method's definition, there is something like the code A(A()), which is undefined).
As mentioned elsewhere, adding a move constructor to class A with the code A(A&&) = default; will also fix the compilation error (although this fix might not be an option with other, more complex classes). The reason why this removes the error is because there now exists a constructor that emplace() can forward this A() object into (as an argument). That is, the code A(A()) becomes well-defined.

c++17 add class with deleted copy constructor to std::vector at compile time

Below is a class that I've defined with a deleted copy constructor and copy assignment operator. This is the only assumption that has to be made.
class MyClass
{
public:
explicit MyClass(int i) : i(i) {}
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass(MyClass&& other) :
i(std::move(other.i))
{}
MyClass& operator=(MyClass&& other) {
i = std::move(other.i);
return *this;
}
private:
int i;
};
The goal is then to add my class to a std::vector at compile time.
int main()
{
std::vector<MyClass> v{MyClass{0}, MyClass{1}, MyClass{2}};
return 0;
}
My compiler is telling me that the STL requires the use of my deleted copy constructor MyClass::MyClass(const MyClass&) but is there any way around this?
I am already aware of a possible way to add values at runtime but the below is a poor solution in my opinion because I've lost the compile time check.
int main()
{
std::vector<MyClass> v;
v.emplace_back(MyClass{0});
v.emplace_back(MyClass{1});
v.emplace_back(MyClass{2});
return 0;
}
My compiler is telling me that the STL requires the use of my deleted copy constructor MyClass::MyClass(const MyClass&) but is there any way around this?
No, you can't.
initializer_list creates a hidden array for you, which is declared const, roughly evaluated like this:
// pseudo code
const MyClass __arr[3] = { MyClass(1), MyClass(2), MyClass(3) };
std::vector<MyClass> v{ std::initializer_list<MyClass>{ __arr, __arr + 2 } };
If you want to avoid copying, you'll have to stick to emplace_back like you said.
I am already aware of a possible way to add values at runtime ...
By the way, your example is not the proper way to use emplace_back:
std::vector<MyClass> v;
v.emplace_back(MyClass{0});
v.emplace_back(MyClass{1});
v.emplace_back(MyClass{2});
You're still creating MyClass and then move it to v, which is quite a common mistake when using emplace-ish functions.
What you really wanted to do is probably as follows:
v.reserve(3);
v.emplace_back(0);
v.emplace_back(1);
v.emplace_back(2);
This way you're avoiding accidentally invoking move constructor, and just constructing the object at the right place only once with no move, and no copy.
The goal is then to add my class to a std::vector at compile time.
If you want to create an array at compile time, use std::array instead.
std::array is exactly designed for that purpose:
std::array<MyClass, 3> v = { 1, 2, 3 };

Why doesn't this use of emplace_back with deleted copy constructor work?

I have a type that I have deleted the copy constructor from, and I would like to have a vector of this type, so I need to create all the elements via emplace_back. But, emplace_back seems to require a copy constructor, as the compiler gives a warning about not being able to instantiate emplace_back because the copy constructor has been deleted. Why does it need a copy constructor? I thought the whole point of emplace_back was to build the vector without copying anything. Can I even have a vector of objects that don't have a copy constructor?
class MyType {
public:
MyType(std::array<double, 6> a) {}
MyType(const MyType& that) = delete;
};
int main() {
std::vector<MyType> v;
std::array<double, 6> a = {1,2,3,4,5,6};
v.emplace_back(a);
}
Compiler is clang/llvm.
When the vector's internal storage grows, it will need to move the elements from the old storage to the new. By deleting the copy constructor, you also prevent it generating the default move constructor.
To be able to call emplace_back, your type should either be EmplaceConstructible or MoveInsertible .
You need to give a move constructor to your class if you have deleted the copy constructor. (Check this for requirements of emplace_back)
MyType(MyType &&a) {/*code*/} //move constructor
If you try to run this code:
// Example program
#include <iostream>
#include <string>
#include <array>
#include <vector>
class MyType {
public:
MyType(std::array<double, 6> a) {
std::cout<< "constructed from array\n";
}
MyType(const MyType& that){
std::cout<< "copy\n";
}
MyType(MyType&& that){
std::cout<< "move\n";
}
};
int main() {
std::vector<MyType> v;
std::array<double, 6> a = {1,2,3,4,5,6};
v.emplace_back(a);
}
You will get the following result:
constructed from array
Live Demo
It is clear that just the constructor from std::Array is called. So, no need for copy constructor. But in the same time if you deleted the copy constructor, the compiler will raise an error (at least on two compilers I tried first second ). I think that some compilers will check for the existence of copy constructor when using emplace_back even if it is not necessary in this practical case while others won't. I do not know what is standard here (which compiler is right or wrong).

Emplacing in vector using default constructor

I want to use vector::emplace to default construct a non-copyable and non-assignable object and then use specific methods on the object using an iterator to the newly created object. Note that there are no parameterized constructors of the class just the default constructor. A simple example is:
#include <iostream>
#include <vector>
using namespace std;
class Test {
public:
Test() {}
private:
Test(const Test&) = delete; // To make clas un-copyable.
Test& operator=(const Test&) = delete;
int a_;
};
int main() {
vector<Test> test_vec;
test_vec.emplace_back(); // <---- fails
return 0;
}
vector::emplace() constructs a new object but requires arguments to a non-default constructor. vector::emplace_back() will construct at the end of the vector.
Is there a way to emplace with default construction. Is there a way to use piecewise construction or default forwarding perhaps using std::piecewise_construct as there is for maps? For example, in the case of maps, we can use:
std::map<int,Test> obj_map;
int val = 10;
obj_map.emplace(std::piecewise_construct,
std::forward_as_tuple(val),
std::forward_as_tuple());
Is there something similar for vectors?
vector::emplace_back() will construct at the end of the vector but also require arguments.
Parameter packs can be empty. Thus the variadic template emplace_back can be called without arguments; I.e.
vector<VeryLimitedClass> vec;
vec.emplace_back();
Is valid code that initializes an object of type VeryLimitedClass through its default constructor and "emplaces" it at the back of vec.
As pointed out by #dyp and #Casey in the comments, std::emplace will not work for vectors of the Test class as the class is neither movable nor copyable because "the user-declared copy constructor suppresses generation of the default move constructor" (#Casey).
To use emplace here, the class will need to be movable or copyable. We can make the class movable by explicitly defining (and defaulting) the move constructors:
public:
Test(Test&& other) = default;
Test& operator=(Test&& other) = default;
This will also implicitly make the class not-copyable "since declaring the move operations will suppress implicit generation of the copies." (#Casey)
Now we can use std::emplace_back() and then use vector::back() to call methods of the newly created object.
For map, easy:
std::map<int, Object> obj_map;
obj_map[10]; // default-constructs an object with key 10
Otherwise, what you have works too:
obj_map.emplace(std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(args, to, construct, with));
[edit] The equivalent for vector is emplace_back:
obj_vector.emplace_back(); // default construct
obj_vector.emplace_back(args, to, std::move(construct), with); // forward these

error: no matching function for call to default copy constructor?

I have a std::map container variable in my class that is populated with objects of my nested-class:
class Logger {
private:
//...
class Tick{
///stores start and end of profiling
uint32_t start, lastTick,total;
/// used for total time
boost::mutex mutexTotalTime;
///is the profiling object started profiling?
bool started;
public:
Tick(){
begin();
}
/*
Tick(const Tick &t){
start = t.start;
lastTick = t.lastTick;
total = t.total;
started = t.started;
}
*/
uint32_t begin();
uint32_t end();
uint32_t tick(bool addToTotalTime = false);
uint32_t addUp(uint32_t value);
uint32_t getAddUp();
};
std::map<const std::string, Tick> profilers_;
//...
public:
//...
Logger::Tick & Logger::getProfiler(const std::string id)
{
std::map<const std::string, Tick>::iterator it(profilers_.find(id));
if(it != profilers_.end())
{
return it->second;
}
else
{
profilers_.insert(std::pair<const std::string, Tick>(id, Tick()));
it = profilers_.find(id);
}
return it->second;
}
//...
};
the above code will not compile if I dont provide a copy constructor while I thought the default copy constructor should be already in place?!
Am I missing any concept?
thanks
The copy constructor may be generated for you only if all members of your class are copyable. In the case of Tick you have an object
boost::mutex mutexTotalTime;
which is not copyable, so the compiler will not generate the copy constructor. Observe that in your commented out copy constructor you do not copy the mutex - because you know you should not. The compiler does not know that.
As a side note, there is no need to explicitly say const for map keys:
std::map<const std::string, Tick> profilers_;
Map keys are always const, and your declaration is completely equivalent to
std::map<std::string, Tick> profilers_;
The problem is that boost::mutex is not copyable. So if you don't provide a copy constructor, the compiler attempts the generate a default one. That default one needs to copy all of the members, but cannot copy boost::mutex, so it gives up. Your copy constructor does not copy the mutex. Instead it default initializes the new one, so this works.
boost::mutex is non-copyable. Since Tick has one as a data member, this makes Tick also non-copyable. This in turn makes the map non-copyable.
So to make Logger copyable, you have to provide your own copy constructor and implement an appropriate duplication of profilers_ in it. Or, perhaps even more appropriately (thanks to #LightnessRacesInOrbit for the suggestion), provide an appropriate copy constructor for Tick instead.