std::forward_as_tuple 的使用案例

6

Cppreference为std::forward_as_tuple提供了以下示例(见此处

#include <iostream>
#include <map>
#include <tuple>
#include <string>

int main()
{
     std::map<int, std::string> m;

     m.emplace(std::piecewise_construct,
               std::forward_as_tuple(10),
               std::forward_as_tuple(20, 'a'));
     std::cout << "m[10] = " << m[10] << '\n';

     // The following is an error: it produces a
     // std::tuple<int&&, char&&> holding two dangling references.
     //
     // auto t = std::forward_as_tuple(20, 'a');
     // m.emplace(std::piecewise_construct, std::forward_as_tuple(10), t);
}

相对于简单编写代码而言,这样做的好处是什么?

 m.emplace(std::make_pair(20,std::string(20,'a')));
1个回答

8

它避免了创建不必要或潜在不可能的对象拷贝。

首先,让我们考虑一个值类型,它不同于std::string。我将使用一个无法被复制的东西,但这也适用于那些可以被复制但复制代价昂贵的东西:

struct NonCopyable
{
    NonCopyable(int a, char b) {} // Dummy
    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'} 对象 - 303
@303 这正是 try_emplace 所做的事情,但它直到 C++17 才被添加。emplace 直接构造映射所持有的 std::pair 条目,而 try_emplace 则抽象出了 std::pair 并采用键和要转发以就地构造值的参数。 - Miles Budnek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接