2012-06-08 2 views
13

Рассмотрим:Должен ли я std :: переместить shared_ptr в конструкторе перемещения?

#include <cstdlib> 
#include <memory> 
#include <string> 
#include <vector> 
#include <algorithm> 
#include <iterator> 
using namespace std; 

class Gizmo 
{ 
public: 
    Gizmo() : foo_(shared_ptr<string>(new string("bar"))) {}; 
    Gizmo(Gizmo&& rhs); // Implemented Below 

private: 
    shared_ptr<string> foo_; 
}; 

/* 
// doesn't use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(rhs.foo_) 
{ 
} 
*/ 


// Does use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(std::move(rhs.foo_)) 
{ 
} 

int main() 
{ 
    typedef vector<Gizmo> Gizmos; 
    Gizmos gizmos; 
    generate_n(back_inserter(gizmos), 10000, []() -> Gizmo 
    { 
     Gizmo ret; 
     return ret; 
    }); 

    random_shuffle(gizmos.begin(), gizmos.end()); 

} 

В приведенном выше коде, есть две версии Gizmo::Gizmo(Gizmo&&) - один использует std::move на самом деле движениеshared_ptr, а другие просто копирует shared_ptr.

Оба варианта, похоже, работают на поверхности. Одна разница (единственная разница, которую я вижу) находится в версии не move, счетчик ссылок shared_ptr временно увеличивается, но только ненадолго.

Я бы обычно пошел дальше и shared_ptr, но только для того, чтобы быть четким и последовательным в моем коде. Я пропустил здесь рассмотрение? Должен ли я предпочесть одну версию над другой для любой технической причины?

+2

Перемещение в конструкторе перемещения, по крайней мере, семантически непротиворечиво ... – ildjarn

+1

Почему вы сохраняете строку в shared_ptr? Share_ptr как переменная-член чаще всего является признаком плохого дизайна. –

+1

Перемещение в конструкторе перемещения соответствует тому, что автоматически генерирует компилятор. –

ответ

16

Основная проблема здесь заключается не в небольшой разнице в производительности за счет дополнительного атомного прироста и уменьшения в shared_ptr, но что семантика операции несовместима, если вы не выполняете движение.

Хотя предполагается, что счетчик ссылок shared_ptr будет временным, на этом языке нет такой гарантии. Исходный объект, из которого вы перемещаетесь, может быть временным, но он также может иметь гораздо более продолжительный срок службы. Это может быть названа переменная, которая была литая к RValue ссылок (скажем std::move(var)), в этом случае, не движущихся от shared_ptr вы все еще сохраняя совместно собственность с источником движения, и если пункт назначения shared_ptr имеет меньшую область действия, тогда время жизни остроконечного объекта будет излишне расширенным.

+0

Интересно, когда вы используете ходы, в какой мере мы должны думать о себе, «этот ход может ухудшиться до копии»? Очевидно, мы делаем это при написании кода шаблона для произвольного типа 'MoveConstructible'' T', так как на самом деле он вообще не имеет конструктора перемещения, не говоря уже о том, что он модифицирует источник. Очевидно, что это плохой QoI, если перемещенный объект не требует ресурсов, поэтому, если 'Gizmo' имеет конструктор перемещения, то он должен быть хорошим. Но я думаю, что это вопрос конвенции и стиля кодирования, как крест мы имеем право быть, когда это не так. –

+0

+1, вот что я получил в своем комментарии к ответу Джеймса. Хорошо сказано. – ildjarn

+0

Для другого примера, правильной реализацией назначения перемещения является вызов 'swap'. Состояние перемещенного объекта не определено, и поэтому, в частности, разрешено быть состоянием перемещенного объекта. Но вы были бы немного озадачены тем, что ресурсы * перемещенного объекта * висят бесконечно. –

11

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

+0

Существуют также семантические различия: можно ли ожидать перехода от 'Gizmo', чтобы сохранить внутреннее общее состояние? Лично я нашел бы это удивительным и ожидал бы перенесенный объект, чтобы освободить все разделяемое состояние. – ildjarn

+1

@ildjarn: Я согласен, хотя это никак не повлияет на правильность программы: перемещенный из объекта все равно будет разрушаемым и назначаемым. –

13

Я поддержал ответ Джеймса Макнеллиса. Я хотел бы прокомментировать его ответ, но мой комментарий не будет соответствовать формату комментария. Поэтому я помещаю его здесь.

Восхитительный способ измерения воздействия перемещения shared_ptr против копирования - это использовать что-то вроде vector<shared_ptr<T>>, чтобы переместить или скопировать целую кучу и время. Большинство компиляторов имеют возможность включить/выключить семантику перемещения, указав языковой режим (например, -std = C++ 03 или -std = C++ 11).

Вот код, который я только что протестировал на -O3:

#include <chrono> 
#include <memory> 
#include <vector> 
#include <iostream> 

int main() 
{ 
    std::vector<std::shared_ptr<int> > v(10000, std::shared_ptr<int>(new int(3))); 
    typedef std::chrono::high_resolution_clock Clock; 
    typedef Clock::time_point time_point; 
    typedef std::chrono::duration<double, std::micro> us; 
    time_point t0 = Clock::now(); 
    v.erase(v.begin()); 
    time_point t1 = Clock::now(); 
    std::cout << us(t1-t0).count() << "\u00B5s\n"; 
} 

Использование лязг/LibC++ и -std = C++ 03 печатается для меня:

195.368µs 

Переключение на - std = C++ 11 Получаю:

16.422µs 

Ваш пробег может отличаться.

+2

+1: Просто наблюдение. Когда я адаптировал это для использования моего класса «Gizmo» выше, версии «move» и non-'move' были почти одинаковыми. Это было на MSVC10, используя 'boost :: chrono', а не' std :: chrono', скомпилированный x64 Release. –

+0

@JohnDibling: Интересно. Если вы выясните, почему существует такое несоответствие в наших результатах, я бы хотел услышать об этом. Одна вещь, которую нужно попробовать: Поместите noexcept в свой конструктор перемещения. Я не знаю, реализует ли это MSVC10 это или нет. Я был бы удивлен, если бы подумал о том, как поздно ничего не произошло. И я действительно не ожидал, что это изменит элемент vector :: erase. Но, тем не менее, это первое, что я попробую. Я работаю на 2,8 ГГц Intel Core i5 (скомпилирован для 64 бит). Были ли ваши результаты порядка нескольких сотен микросекунд или несколько десятков микросекунд? –

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