C ++ скопировать-создать экземпляр в карту - PullRequest
1 голос
/ 13 февраля 2012

Вот код с простым классом Foo, который создается и затем вставляется в карту. Я не понимаю, как вызывается конструктор копирования при вставке foo в fooMap.

#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, что приведет к выводу:

Copying const Foo 'bar' into Foo 'Undefined0'

Ответы [ 4 ]

4 голосов
/ 13 февраля 2012

Вы выполняете задание, а не копируете конструкцию в строке fooMap["bar"] = foo; Переопределите Foo &operator=(const Foo&), чтобы увидеть, что происходит.

Конструкция по умолчанию - это создание исходного элемента, в который вы будете выполнять назначение.

Копии, которые вы видите, вероятно, являются внутренними для реализации карты - помещая ее в нужное место в дереве. Есть больше, чем я ожидал.

2 голосов
/ 13 февраля 2012

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=, потому что элемент карты для этого ключа уже создан, и только его значение изменяется посредством его ссылки.

0 голосов
/ 13 февраля 2012

fooMap["bar"] = foo приводит к построению по умолчанию, двум копиям и назначению.

Требуется конструкция по умолчанию, так как для ключа "bar".

не существует пары строка-foo.

Первая копия создается, когда пара создается с ключом "bar", а значение копируется из созданного по умолчанию foo, который только что был создан.

Вторая копия появляется, когда пара копируется в карту.

Назначение затем присваивает значение на карте foo.

Реализация STL, использующая семантику R-Value в C ++ 11, могла бы сделать это намного эффективнее.

0 голосов
/ 13 февраля 2012

Хороший вопрос, у меня такое же поведение. Специфика наших (и, может быть, всех?) Реализаций STL создает объекты по умолчанию, а затем использует другой метод для копирования данных.

Стоя в стороне, можно увидеть, что как только метод вставки (или, в вашем случае, оператора []) был вызван, копия исходного объекта существует на карте , и это вполне может быть все, что обещает стандарт.

...