c ++ copy-construct实例到map中

时间:2012-02-13 12:45:47

标签: c++ map copy-constructor

这是一个带有简单类Foo的代码,它在instanciated中然后插入到地图中。 我不明白在将foo插入fooMap时如何调用copy-constructor。

#include <stdlib.h>
#include <stdio.h>

#include <map>
#include <iostream>

using namespace std;

class Foo {
    public:

        //default constructor
        Foo() {
            name_ = string("Undefined") + suffix();
            cout << "Constructing Foo '"<< name_ << "'" << endl;
        };

        //constructor with arg
        Foo(const char* name): name_(name) {
            cout << "Constructing Foo '" << name << "'" << endl;
        };

        //default const copy-constructor
        Foo(const Foo &foo) {
            name_ = foo.get_name() + suffix();
            cout << "Copying const Foo '" << foo.get_name() << "' into Foo '" << name_ << "'" << endl;
        }

        //default destructor
        ~Foo() {
            cout << "Destroying Foo '" << name_ << "'" << endl;
        }

        //getting name
        const string get_name() const {
            return name_;
        };

        //setting name
        void set_name(string new_name){
            name_ = new_name;
        }

        //suffix for name
        string suffix() {
            static int cmp=0;
            char ch[2];
            sprintf(ch,"%d",cmp++);
            return string(ch);
        }

    private:
        string name_;
};

int main() {

    typedef map<string, Foo> FooMapType;
    FooMapType fooMap;

    cout << "1:\n";
    Foo foo("bar");

    cout << "\n2:\n";
    fooMap["bar"] = foo;

    cout << "\n3:\n";
    cout << fooMap["bar"].get_name() << endl;
    foo.set_name("baz");

    cout << "\n4:\n";

输出结果为:

1:
Constructing Foo 'bar'

2:
Constructing Foo 'Undefined0'
Copying const Foo 'Undefined0' into Foo 'Undefined01'
Copying const Foo 'Undefined01' into Foo 'Undefined012'
Destroying Foo 'Undefined01'
Destroying Foo 'Undefined0'

3:
bar

4:
Destroying Foo 'baz'
Destroying Foo 'bar'

但是我期望调用foo copy-constructor,导致输出:

Copying const Foo 'bar' into Foo 'Undefined0'

4 个答案:

答案 0 :(得分:4)

您正在执行作业,而不是在fooMap["bar"] = foo;覆盖Foo &operator=(const Foo&)行中复制构造以查看发生的情况。

默认构造是创建您要进行分配的初始项目。

您看到的副本可能是地图实施的内部 - 将其放入树中的适当位置。这比我想象的要多。

答案 1 :(得分:2)

map::operator[]返回对值成员的引用,因此声明如下:

fooMap["bar"] = foo;

可以解释为:

Foo& fooRef = fooMap["bar"]; // (1)
fooRef = foo;                // (2)

在(1)执行map::operator[]并执行(2)Foo::operator=

如果映射中不存在给定键,map::operator[]将创建一个新元素 - 由提供的键构成的对和由默认构造函数构造的值对象。 (在您的示例中,键类型为std::string,值类型为Foo)。如果存在给定键,则它只返回对值对象的引用。

第(2)行通过引用改变值。

如果您看一下map::operator[]的实现(我在这里提供Microsoft的一个,来自VS2010附带的STL库),您可以看到它调用map::insert() 在引擎盖下,提供临时对象value_type,它再次从临时对象mapped_type创建:

mapped_type& operator[](const key_type& _Keyval)
{   
    // find element matching _Keyval or insert with default mapped
    iterator _Where = this->lower_bound(_Keyval);

    if (_Where == this->end() || this->comp(_Keyval, this->_Key(_Where._Mynode())))
       _Where = this->insert(_Where, value_type(_Keyval, mapped_type()));

    return ((*_Where).second);
}    

value_type基本上是我上面提到的对,mapped_type在你的情况下是Foomapped_type()通过调用其默认构造函数来创建临时对象。这与输出中的第二个默认构造函数匹配(第一个在创建局部变量foo时调用)。 value_type的构造函数使用mapped_type的复制构造函数来创建value成员的实例。这匹配Foo的复制构造函数的第一次调用。在您的日志中还有另一个此构造函数的调用,为了找到它的来源,我们需要深入研究map::operator[] ...实际上它调用的insert方法:

template<class _Valty>
typename _STD tr1::enable_if<!_STD tr1::is_same<const_iterator, typename _STD tr1::remove_reference<_Valty>::type>::value, iterator>::type
insert(const_iterator _Where, _Valty&& _Val)
{   
   // try to insert node with value _Val using _Where as a hint
   return (_Insert(_Where, this->_Buynode(_STD forward<_Valty>(_Val))));
}

Map以树形式实现,我们可以看到内部_Insert方法插入了一个用_Buynode()创建的树节点的新实例:

template<class _Valty>
_Nodeptr _Buynode(_Valty&& _Val)
{   
   // allocate a node with defaults
   _Nodeptr _Wherenode = _Buynode();
   ...
   _Cons_val(this->_Alval, _STD addressof(this->_Myval(_Wherenode)), _STD forward<_Valty>(_Val));
   ...
   return (_Wherenode);
}

Tree节点封装了pair对象 - map的元素,因此它的创建涉及创建另一个副本 - _Val,这就是Foo复制构造函数的另一个调用发生的地方。

map::operator[]调用创建了两个包含Foo的临时对象,并且在返回时这些对象被销毁,因此您可以在输出中看到两个析构函数调用。

如果你实现Foo::operator=并在其中加入一条跟踪,你就可以看到也调用了这个方法(第(2)行)。

如果稍后将相同的键映射到其他Foo对象,则只执行Foo::operator=因为已创建该键的map元素,并且只通过其引用更改了值。

答案 2 :(得分:0)

一个好问题,我得到了同样的行为。我们(可能还有所有?)STL实现的细节创建默认对象,然后使用另一种方法复制数据。

站在后面,可以看出,一旦插入(或在你的情况下是operator [])方法被调用原始对象的副本确实存在于地图中,那很可能是标准所承诺的一切。

答案 3 :(得分:0)

fooMap["bar"] = foo会产生默认构造,两个副本和一个分配。

默认构造是必需的,因为键"bar"没有string-foo对。

第一个副本出现时,一对是使用键"bar"构建的,并且值是从刚刚构建的默认构造的foo复制的。

然后第二个副本出现,然后将该对复制到地图中。

然后,作业会将地图中的值指定给foo

在C ++ 11中使用R-Value语义的STL实现可以提高效率。