我应该std ::在移动构造函数中移动shared_ptr吗?

Joh*_*ing 14 c++ shared-ptr rvalue-reference c++11

考虑:

#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());

}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,有两个版本Gizmo::Gizmo(Gizmo&&)-一个使用std::move真正移动shared_ptr,而另一个只是复制了shared_ptr.

这两个版本似乎都在表面上工作.一个区别(我能看到的唯一区别)是在非move版本shared_ptr中暂时增加了引用计数,但只是暂时增加.

我通常会继续和moveshared_ptr,但只能是明确和一贯的在我的代码.我在这里错过了考虑吗?出于技术原因,我是否应该优先考虑另一个版本?

Dav*_*eas 17

这里的主要问题不是由于额外的原子增量和减量导致的小的性能差异,shared_ptr而是除非你执行移动,否则操作的语义是不一致的.

虽然假设shared_ptr遗嘱的引用计数只是暂时的,但语言中没有这样的保证.您要移动的源对象可以是临时对象,但它也可以具有更长的生命周期.这可能是因为已经铸造到一个名为可变右值引用(比方说std::move(var)),在这种情况下,通过不移动shared_ptr您还在维护共享与移动源的所有权,如果目的地shared_ptr有一个较小的范围内,则不必要地延长尖头物体的寿命.

  • @David:我刚刚指出在具有严格指针安全性的实现上,原始指针可能实现共享所有权(例如通过标记清除垃圾收集器).此外,由于MoveConstructible和MoveAssignable如何在标准中定义,术语"可移动"可能是模糊的.它可能意味着"实际上可以移动",或者可能意味着"可以移动或复制".因此,不同的人会因不同的意义而感到惊讶. (2认同)

How*_*ant 15

我赞成了James McNellis的回答.我想对他的答案发表评论,但我的评论不符合评论格式.所以我把它放在这里.

一种有趣的方法来衡量移动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";
}
Run Code Online (Sandbox Code Playgroud)

使用clang/libc ++和-std = c ++ 03这个打印出来给我:

195.368µs
Run Code Online (Sandbox Code Playgroud)

切换到-std = c ++ 11我得到:

16.422µs
Run Code Online (Sandbox Code Playgroud)

你的旅费可能会改变.

  • +1:只是一个观察.当我改编它以使用上面的'Gizmo`类时,`move`和non`move`版本的时间几乎相同.这是在MSVC10上,使用`boost :: chrono`而不是`std :: chrono`,编译x64 Release. (2认同)

Jam*_*lis 11

使用move是优选的:它应该比副本更有效,因为它不需要额外的原子增量和引用计数的减少.