在std :: map中优化创建新的空元素

问题描述 投票:-2回答:2

为插入新元素,std::map需要存在std::pair对象。我没有找到另一种方法来在std::map中添加新元素而不构造这样的std::pair对象。

但是假设std::map值是一个消耗大量资源的重物。将这样的元素插入std::map会限制一个不必要的副本。在我有一些对象X需要直接复制到std::map的情况下,std作者限制我分两步执行这个副本:

  1. 复制X-> std::pair
  2. 复制对obj - > std::map插入位置。

如何消除这些不需要的副本?实现Move构造函数不能解决我的问题 - 副本仍然存在。

我怀疑std::map中的一些方法,它插入一个带有一些键但没有值的新空元素,而不调用任何值的构造函数。然后我可以按照自己的方式移动值。但我在std::map找不到这样的方法!

c++ c++11 stdmap std-pair
2个回答
2
投票

std::map值是一个耗费大量资源的重物。将这样的元素插入std::map会限制一个不必要的副本。如何消除这些不需要的副本?

你是对的,std::map::emplace是我们现有的唯一解决方案。关于使用std::map::emplacecppreference.com的一个小解释如下:

小心使用emplace可以构建新元素,同时避免不必要的复制或移动操作。调用新元素(即std::pair<const Key, T>)的构造函数与提供给emplace的参数完全相同,通过std::forward<Args>(args)....转发

这意味着,当您在类中为承包商提供完全相同的参数时,可以通过std::map::emplace在地图插入时(即,而不是构造和复制)就地构建类实例。然后,你需要使用std::map::emplace以及std::piecewise_constructstd::forward_as_tuple(假设该类包含多个成员)。

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*/)
    );

为了演示上述情况,我制作了一个小样本代码,其中ClassA的成员将在不调用任何特殊成员函数的情况下构建。为了确保,我已经禁用了defaultcopymove构造函数。

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;
}

输出:

A class: C'tor called...!
B class: C'tor called...!

更新:另一方面,

即使在容器中已经存在具有键的元素,也可以构造该元素,在这种情况下,新构造的元素将立即被销毁。

这意味着,您的期望(或假设):

“我怀疑std::map中的一些方法,它插入一个带有一些键但没有值的新空元素,而不调用任何值的构造函数。然后我可以按照我自己的方式移动值。”

通过上面提到的std::map::emplace方式是不可能实现的。正如@aschepler在评论中指出的那样,通过使用C ++ 17特性std::optional,你可以拥有有时(可选)键的映射而没有值。

为此,您需要将值设置为可选,如下所示,当然编译器版本支持C ++ 17或更高版本。

std::map<Key, std::optional<Value>> myMap;

现在您可以在代码中随时构造一个对象,一段时间后,您可以将其移动到适当的键值(即条目)。最后但并非最不重要的,不要忘记提供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;
}

输出:

A class: C'tor called...!
B class: C'tor called...!

3
投票
© www.soinside.com 2019 - 2024. All rights reserved.