这是一个带有简单类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'
答案 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
在你的情况下是Foo
。
mapped_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实现可以提高效率。