【问题标题】:std::unordered_map::emplace behavior with no move/copy constructor没有移动/复制构造函数的 std::unordered_map::emplace 行为
【发布时间】:2014-12-30 02:42:11
【问题描述】:

我试图更好地理解 std::unordered_map::emplace,并且我想我理解如果存在复制和移动构造函数,它们是如何被使用的。我在下面概述了每种不同用法的相关描述。如果有人发现描述有任何问题,请告诉我。

然而,我最好奇的是,当只定义默认和用户定义的构造函数时会发生什么?貌似根本没有调用默认构造函数,而自定义构造函数只调用了ONCE,那么unordered_map中新构造元素的FooBar成员是怎么填充的呢? (我想默认/用户定义的构造函数至少被调用两次)。另外,如果 FooBar 没有定义复制和移动构造函数,那么下面 3 种情况是否有任何行为差异?

注意:我知道这是一个微不足道的例子,深拷贝不是问题,所以复制/移动语义并没有真正产生任何显着的收益。我只是使用这个简化的例子来表达我的观点。

struct FooBar
{
    FooBar()
    {
        printf("Foobar default constructor called\n");
    };

    FooBar(int* pFoo, int* pBar)
    {
        m_pFoo = pFoo;
        m_pBar = pBar;
        printf("Foobar user-defined constructor called\n");
    };

    FooBar(FooBar & rhs)
    {
        m_pBar = rhs.m_pBar;
        m_pFoo = rhs.m_pFoo;
        printf("Foobar copy constructor called\n");
    };

    FooBar(FooBar && rhs)
    {
        m_pBar = rhs.m_pBar;
        m_pFoo = rhs.m_pFoo;
        rhs.m_pBar = nullptr;
        rhs.m_pFoo = nullptr;
        printf("Foobar move constructor called\n");
    };

    int* m_pFoo;
    int* m_pBar;
};


int _tmain(int argc, _TCHAR* argv[])
{
    std::unordered_map<int, FooBar> map;

    //template< class... Args >
    //std::pair<iterator, bool> emplace(Args&&... args);

    // 1. 
    // Description: A lvalue of foobar1 is temporarily created, initialized, copied (via copy constructor)
    // to supply the in-place constructed element's FooBar member, and destroyed
    // Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar copy constructor called
    // Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
    {
        FooBar foobar1 = {(int*)0xDEADBEEF, (int*)0x01010101};
        map.emplace(10, foobar1);
    }

    // 2. 
    // Description: A rvalue of bar1 is temporarily created, initialized, moved (via move constructor)
    // to supply the in-place constructed element's FooBar member, and destroyed
    // Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar move constructor called
    // Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
    map.emplace(20, FooBar{(int*)0xDEADBEEF,(int*)0x01010101});

    // 3. 
    // Description: A lvalue of foobar1 is temporarily created and initialized. It is then
    // explicitly converted to a rvalue (via std::move), moved (via move constructor) to supply 
    // the in-place constructed element's FooBar member, and destroyed
    // Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar move constructor called
    // Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
    {
        FooBar foobar2 = {(int*)0xDEADBEEF, (int*)0x01010101};
        map.emplace(30, std::move(foobar2));
    }

    return 0;
}

谢谢。

【问题讨论】:

  • FooBar(int* pFoo, int* pBar) 不是“默认”构造函数。
  • 您是否建议在创建地图中新构造的元素时调用默认构造函数而不是FooBar(int* pFoo, int* pBar)?如果是这样,我如何覆盖该构造函数以查看消息?我只尝试了FooBar(),但也没有被调用。
  • 不,我只是建议您的问题写得不准确,因为您所谓的“默认构造函数”不是。
  • 好的,我会更新术语以使其更准确。

标签: c++ visual-c++ c++11 unordered-map


【解决方案1】:

您似乎对基本术语有一些误解。 默认构造函数是可以不带参数调用的构造函数。 FooBar(int* pFoo, int* pBar) 不是默认构造函数。

此外,为了让您的代码在 gcc 和 clang 上编译,我不得不将您的复制构造函数修改为 FooBar(FooBar const&amp; rhs)

现在,当您在 cmets 中说复制/移动构造函数不存在时,我怀疑您只是在删除它们的定义。但这并不意味着它们不存在,编译器会为您隐式定义一个(请注意,VS2013 不会隐式定义移动构造函数,因为它缺少该功能)。

当您调用unordered_map::emplace 时,参数将被转发以构造一个unordered_map::value_type 对象,即std::pair&lt;const Key, Value&gt;,因此对emplace 的调用最终会调用std::pair constructor

在案例 1 中,您创建了一个名为 foobar1 的对象,并且对 emplace 的调用调用 FooBar 复制构造函数。

在情况 2 中,您创建的临时 FooBar 对象将被移动,即调用 FooBar 移动构造函数,假设存在一个。如果没有,将调用复制构造函数。

案例 3 与案例 2 相同,因为通过调用 std::move,您允许调用移动构造函数,并允许移动 foobar2 对象。


如果您不希望在将对象放置到地图中时使用复制/移动构造函数,请使用 std::pair 的分段构造构造函数。

map.emplace(std::piecewise_construct,
            std::forward_as_tuple(40),
            std::forward_as_tuple((int*)0xDEADBEEF, (int*)0x01010101));

这应该打印一行

Foobar default constructor called

Live demo

【讨论】:

  • 真的不是误会。我有一个默认构造函数,但我修改它以获取参数而不更改其命名。我的错。我已经解决了。所以基本上简短的回答是,移动/复制构造函数是由编译器为你定义的。看起来您对 1、2 和 3 的描述也与我的 cmets 的描述相符。感谢您的回答!
  • @lancery 不客气,我添加了一个示例,说明如何在不调用复制/移动构造函数的情况下放置对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多