Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
128 views
in Technique[技术] by (71.8m points)

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

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:

  1. copy X->std::pair
  2. 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!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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...!
";
    }
    // 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...!
";
    }
    // 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...!

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...