如何最好地将 emplace 与 std::map 一起使用
How best to use emplace with std::map
我正在尝试将 emplace() 与 std::map 一起使用而不是插入。我有一个简单的测试程序如下:
#include <iostream>
#include <map>
#include <string>
class CTest
{
public:
CTest() : Value(0), Name() { std::cout << "Default constructor" << std::endl; };
CTest(int val, const std::string &name) : Value(val), Name(name) {
std::cout << "Parameterized constructor" << std::endl; }
CTest(const CTest& test) {
Value = test.Value; Name = test.Name; std::cout << "Copy Constructor" << std::endl; }
CTest(CTest&& test) noexcept {
Value = test.Value; Name = test.Name; std::cout << "Move Constructor" << std::endl; }
CTest& operator=(const CTest& test) {
Value = test.Value; Name = test.Name; std::cout << "Copy assignment" << std::endl; return *this; }
CTest& operator=(CTest &&test) noexcept {
Value = test.Value; Name = test.Name; std::cout << "Move assignment" << std::endl; return *this; }
~CTest() { std::cout << "Destructor" << std::endl; }
private:
int Value;
std::string Name;
};
int main()
{
CTest t1(1, "hello");
CTest t2(2, "hello");
std::map<int, CTest> testMap;
testMap[1] = t1; //1
testMap.emplace(2, t2); //2
testMap.emplace(3, CTest(3, "hello")); //3
testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(4, "hello")); //4
testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(std::move(t1))); //5
return 0;
}
每一个的输出是:
1
默认构造函数
复制赋值
2
复制构造函数
3
参数化构造函数
移动构造函数
析构函数
4
参数化构造函数
5
移动构造函数
析构函数
1涉及最多的复制:使用默认构造函数在映射中创建一个条目,然后进行复制赋值。我很惊讶地看到对 3 和 5 的析构函数调用。在这两种情况下,传递的值都是右值。那么传入的右值是不是创建了一个temporary,用完就删除了?这就引出了一个问题,使用 emplace 的正确方法是什么?是否应该像 4 那样只传递构造函数的参数?正如我的结果所示,这是性能最好的。
Should you just pass the arguments of the constructor
是的,因为这确实是所有 emplace()
函数的设计目的。使用 insert()
,您必须构建一个对象,然后 [通常] 将其复制到您的容器中。通常,如果您使用的是容器,那么您只是在构建,以便可以将它们放入容器中。正如您在测试中看到的那样,这是一些额外的工作。
emplace()
旨在让您直接构建到容器中。您可以通过向 emplace 函数提供构造函数参数来实现。 insert()
如果您已经有了一个对象并想将其放入容器中,则使用。
我有一个尖刻的评论,其他人注意到值得多解释一下。如果您的 class(我称之为 Foo
)具有单参数构造函数,看起来您可以通过将单个参数传递给类似 emplace()
来做与 emplace()
相同的事情 insert()
或 push_back()
或 任何 将 Foo 作为参数的地方。这是一个 'feature' 的语言,编译器会隐式地为你构造一个 Foo
并使用它。问题是在引擎盖下,它并没有做同样的事情。其中 emplace()
将直接在容器中构建您的对象,通过利用单个参数构造函数来伪造它仍然会导致创建副本。另一个需要考虑的缺点是这种隐式转换。它会损害代码的可读性,或者更糟的是,破坏东西。这可以通过将构造函数标记为 explicit
.
来避免
我正在尝试将 emplace() 与 std::map 一起使用而不是插入。我有一个简单的测试程序如下:
#include <iostream>
#include <map>
#include <string>
class CTest
{
public:
CTest() : Value(0), Name() { std::cout << "Default constructor" << std::endl; };
CTest(int val, const std::string &name) : Value(val), Name(name) {
std::cout << "Parameterized constructor" << std::endl; }
CTest(const CTest& test) {
Value = test.Value; Name = test.Name; std::cout << "Copy Constructor" << std::endl; }
CTest(CTest&& test) noexcept {
Value = test.Value; Name = test.Name; std::cout << "Move Constructor" << std::endl; }
CTest& operator=(const CTest& test) {
Value = test.Value; Name = test.Name; std::cout << "Copy assignment" << std::endl; return *this; }
CTest& operator=(CTest &&test) noexcept {
Value = test.Value; Name = test.Name; std::cout << "Move assignment" << std::endl; return *this; }
~CTest() { std::cout << "Destructor" << std::endl; }
private:
int Value;
std::string Name;
};
int main()
{
CTest t1(1, "hello");
CTest t2(2, "hello");
std::map<int, CTest> testMap;
testMap[1] = t1; //1
testMap.emplace(2, t2); //2
testMap.emplace(3, CTest(3, "hello")); //3
testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(4, "hello")); //4
testMap.emplace(std::piecewise_construct, std::forward_as_tuple(4), std::forward_as_tuple(std::move(t1))); //5
return 0;
}
每一个的输出是:
1
默认构造函数
复制赋值
2
复制构造函数
3
参数化构造函数
移动构造函数
析构函数
4
参数化构造函数
5
移动构造函数
析构函数
1涉及最多的复制:使用默认构造函数在映射中创建一个条目,然后进行复制赋值。我很惊讶地看到对 3 和 5 的析构函数调用。在这两种情况下,传递的值都是右值。那么传入的右值是不是创建了一个temporary,用完就删除了?这就引出了一个问题,使用 emplace 的正确方法是什么?是否应该像 4 那样只传递构造函数的参数?正如我的结果所示,这是性能最好的。
Should you just pass the arguments of the constructor
是的,因为这确实是所有 emplace()
函数的设计目的。使用 insert()
,您必须构建一个对象,然后 [通常] 将其复制到您的容器中。通常,如果您使用的是容器,那么您只是在构建,以便可以将它们放入容器中。正如您在测试中看到的那样,这是一些额外的工作。
emplace()
旨在让您直接构建到容器中。您可以通过向 emplace 函数提供构造函数参数来实现。 insert()
如果您已经有了一个对象并想将其放入容器中,则使用。
我有一个尖刻的评论,其他人注意到值得多解释一下。如果您的 class(我称之为 Foo
)具有单参数构造函数,看起来您可以通过将单个参数传递给类似 emplace()
来做与 emplace()
相同的事情 insert()
或 push_back()
或 任何 将 Foo 作为参数的地方。这是一个 'feature' 的语言,编译器会隐式地为你构造一个 Foo
并使用它。问题是在引擎盖下,它并没有做同样的事情。其中 emplace()
将直接在容器中构建您的对象,通过利用单个参数构造函数来伪造它仍然会导致创建副本。另一个需要考虑的缺点是这种隐式转换。它会损害代码的可读性,或者更糟的是,破坏东西。这可以通过将构造函数标记为 explicit
.