没有移动/复制构造函数的std :: unordered_map :: emplace行为

时间:2014-12-30 02:42:12

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

我试图更好地理解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;
}

感谢。

1 个答案:

答案 0 :(得分:4)

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

此外,为了让你的代码在gcc和clang上编译,我必须将你的拷贝构造函数修改为FooBar(FooBar const& rhs)

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

当您致电unordered_map::emplace时,会转发参数以构建unordered_map::value_type对象,即std::pair<const Key, Value>,因此对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