map::operator[]
возвращает ссылку на элемент значения, поэтому следующий оператор:
fooMap["bar"] = foo;
можно интерпретировать как:
Foo& fooRef = fooMap["bar"]; // (1)
fooRef = foo; // (2)
In (1) map::operator[]
выполняется и в (2) Foo::operator=
.
Если данный ключ не существует на карте, map::operator[]
создает новый элемент - пару из предоставленного ключа и объект значения, созданный с помощью конструктора по умолчанию. (Тип ключа std::string
и тип значения Foo
в вашем примере). Если данный ключ существует, он просто возвращает ссылку на объект значения.
Строка (2) изменяет значение через его ссылку.
Если вы посмотрите на реализацию map::operator[]
(я предоставляю ее здесь, из библиотеки STL, поставляемой с VS2010), вы можете увидеть, что она вызывает 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))));
}
Карта реализована в виде дерева, и мы видим, что внутренний метод _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);
}
Узел дерева инкапсулирует парный объект - элемент карты, поэтому его создание включает создание еще одной копии нашей пары - _Val
, и именно в этот момент происходит очередной вызов конструктора копирования Foo
.
map::operator[]
вызов создал два временных объекта, содержащих Foo
, и по возвращении эти объекты были уничтожены, поэтому вы можете увидеть два вызова деструктора в ваших выходных данных.
Если вы внедрите Foo::operator=
и поместите в него трассировку, вы увидите, что этот метод также вызывается (строка (2)).
Если позднее вы отобразите тот же ключ на какой-либо другой объект Foo
, у вас будет выполнено только Foo::operator=
, потому что элемент карты для этого ключа уже создан, и только его значение изменяется посредством его ссылки.