Подтвердить что ты не робот

Должен ли я 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 временно увеличивается, но только кратко.

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Весомым способом измерения влияния производительности на перемещение shared_ptr vs copying является использование чего-то типа vector<shared_ptr<T>> для перемещения или копирования целой группы из них и времени. Большинство компиляторов имеют возможность включить/выключить семантику перемещения, указав языковой режим (например, -std = С++ 03 или -std = С++ 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";
}

Используя clang/libС++ и in -std = С++ 03, это печатает для меня:

195.368µs

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

16.422µs

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

Ответ 3

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