它避免了创建不必要或潜在不可能的对象拷贝。
首先,让我们考虑一个值类型,它不同于std::string
。我将使用一个无法被复制的东西,但这也适用于那些可以被复制但复制代价昂贵的东西:
struct NonCopyable
{
NonCopyable(int a, char b) {}
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
我们如何将其插入到
std::map<int, NonCopyable> m
中?让我们看看可能性:
m.insert({10, NonCopyable{10, 'a'}});
这个无法工作。它接受一个std::pair
的引用并复制它,这需要复制不可复制的NonCopyable
对象,这是不可能的。
m.emplace(10, NonCopyable{10, 'a'}});
这个也不行。虽然它可以就地构造std::pair
,但仍需复制NonCopyable
对象。
m.emplace(std::piecewise_construct,
std::tuple{10},
std::tuple{10, 'a'})
终于有东西可用了。此处std::pair
元素以就地方式构造,其两个子对象也是如此。
接下来再考虑另一种情况。考虑以下类:
struct UsesNonCopyable
{
UsesNonCopyable(const NonCopyable&) {}
UsesNonCopyable(const UsesNonCopyable&) = delete;
UsesNonCopyable& operator=(const UsesNonCopyable&) = delete;
};
现在我们如何向std::map<int, UsesNonCopyable> m
添加元素?
前面两种选项由于与之前的情况相同的原因而无法使用,但是第三种方法也出现了问题:
m.emplace(std::piecewise_construct,
std::tuple{10},
std::tuple{NonCopyable{10, 'a'}})
这个无法工作,原因是NonCopyable
对象必须被复制到传递给std::pair
构造函数的std::tuple
对象中。
这就是std::forward_as_tuple
的用处:
m.emplace(std::piecewise_construct,
std::tuple{10},
std::forward_as_tuple(NonCopyable{10, 'a'}));
这段代码能够工作,因为现在我们不再传递一个包含NonCopyable
对象副本的元组给m.emplace
,而是使用std::forward_as_tuple
构造一个包含对NonCopyable
对象引用的元组。该引用被转发到std::pair
的构造函数中,进而转发到UsesNonCopyable
的构造函数中。
请注意,如果键类型可以复制,C++17的std::map::try_emplace
的添加将消除大部分这种复杂性。以下内容将正常工作并且更加简单:
std::map<int, UsesNonCopyable> m
m.try_emplace(10, NonCopyable{10, 'a'})
m.emplace(10, 10, 'a'); // 在内部构造一个 NonCopyable{10, 'a'} 对象
。 - 303try_emplace
所做的事情,但它直到 C++17 才被添加。emplace
直接构造映射所持有的std::pair
条目,而try_emplace
则抽象出了std::pair
并采用键和要转发以就地构造值的参数。 - Miles Budnek