2013-11-21 3 views
3

Я думал, что следующий пример будет иметь тот же результат, как std::map::emplace(), но это не так:Почему конструктор перемещения C++ 11 копирует значение?

struct MoveTest { 
    MoveTest(): 
     var(true) 
    { 
     cout << "ctor: " << static_cast<void*>(&var) << endl; 
    } 

    MoveTest(MoveTest&& mt): 
     var(std::move(mt.var)) 
    { 
     cout << "move ctor: " << static_cast<void*>(&var) << endl; 
    } 

    bool var; 
}; 


int main() { 
    map<int, MoveTest> mtest; 
    mtest.insert(make_pair(1, MoveTest())); 
    return 0; 
} 

выход:

ctor: 0x7fff12e4e19b 
move ctor: 0x7fff12e4e194 
move ctor: 0x25f3034 

MoveTest::var имеет другой адрес в каждом движении. Это не похоже на «ход». Что не так в моем коде или понимании?

+1

вы не можете проверить перемещение, взяв адрес. то, что перемещено, является внутренней структурой, которая вам не видна. –

+0

Перемещение конструкции не делает то, что вы думаете. То, что он делает, заключается в том, чтобы значение в месте назначения было равно значению изначально на источнике, таким образом, чтобы было разрешено изменять источник. Он не изменяет адрес адресата или источника. Элемент данных 'var' является частью объекта' MoveTest', и его адрес всегда находится где-то внутри объекта, к которому он принадлежит. –

+0

, потому что ваш конструктор движений ничего не движет. копирование bool просто копирует bool, вы получите разные адреса. – yngccc

ответ

5

Для MoveTest, как вы его определили, на самом деле не так много операций, которые могут оптимизировать (или вообще делать), что может действительно сделать, это копирование из исходного пункта назначения.

Перемещение - это большая победа, когда объект содержит указатель на кучу внешней памяти, которая (например) выделена в куче. В этом случае оператор перемещения ctor/assign может в основном выполнять мелкую копию (т. Е. Просто захватить указатель из перемещенного объекта) вместо глубокой копии (копируя все данные, на которые указывает этот указатель).

Например:

class move_tst { 
    int *buffer; 
    static const int size = 1024 * 1024; 
public: 
    move_tst() {   
     buffer = new int[size]; 
     std::iota(buffer, buffer + size, 0); 
    } 

    move_tst(move_tst const &other) { 
     buffer = new int[size]; 
     std::copy_n(other.buffer, size, buffer); 
    } 

#ifdef MOVE 
    move_tst(move_tst &&other) { 
     buffer = other.buffer; 
     other.buffer = nullptr; 
    } 
#endif 

    ~move_tst() { 
     delete [] buffer; 
    } 
}; 

Оговорка: Я использовал необработанный вызов new и сырой указатель здесь так ничего не вовлекается и мы не будем (к примеру) получить движение семантики любезности умный указатель. Для нормального кода при нормальных обстоятельствах вы должны использовать , а не, либо использовать (необработанный указатель или необработанный вызов new, я имею в виду).

Редактировать: насколько это возможно std::move, он фактически не выполняет движение - он просто сигнализирует о том, что вы больше не заботитесь о значении, поэтому он может быть источником движения, даже если это разрушает его ценность.

Например, в описанном выше классе, вы могли бы сделать тест, как:

for (int i = 0; i < 1000; i++) { 
    move_tst src; 
    move_tst dst =src; 
}  

... и сравнить его:

for (int i = 0; i < 1000; i++) { 
    move_tst src; 
    move_tst dst = std::move(src); 
}  

Без std::move, dst будет создана копия src, но с std::move, она сможет совершить переход от src до dst.

+0

Ну, я согласен с тем, что вы сказали. Однако, боюсь, это не отвечает на мой вопрос. Пример в моем вопросе не предназначен для * оптимизации * ничего. Было бы здорово, если бы вы могли объяснить, что не так в моем понимании того, что std :: move делает :) – GuLearn

+0

@ YZ.learner: См. Отредактированный ответ. –

+0

@ YZ.learner Когда я впервые обнаружил 'std :: move', я все время думал о' std :: swap' и путал себя :) – kfsone

1

Ваш журнал просто говорит, что вы создаете временный MoveTest экземпляр с конструктором по умолчанию

MoveTest() 

, который затем копируется в другом месте, внутри пары объекта

make_pair(1, MoveTest()) 

и скопировал снова где-то внутри карты объект

mtest.insert(make_pair(1, MoveTest())); 

Существует три конструктора invoca для трех разных экземпляров MoveTest, каждый со своим адресом. Как уже было объяснено, не являются ли конструкторы конструкторами перемещения или конструкторами копии старого стиля.

Вы ожидали, что некоторые из копий будут оптимизированы? С нетривиальным кодом в конструкторах он становится маловероятным; в частности, чтение & var является хорошей причиной, чтобы избежать ярлыков.

+0

Да, я ожидал, что эти два движка не будут выделять новую память, потому что это то, что нравится «двигаться» (вместо «копировать»). – GuLearn

Смежные вопросы