考虑:
#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
,但只是在我的代码中保持清晰和一致。我在这里错过了考虑吗?对于任何技术原因,我应该更喜欢一个版本吗?
答案 0 :(得分:17)
这里的主要问题不是由于shared_ptr
中额外的原子增量和减量导致的小的性能差异,而是除非您执行移动,否则操作的语义是不一致的。
虽然假设shared_ptr
的引用计数只是暂时的,但语言中没有这样的保证。您要移动的源对象可以是临时对象,但它也可以具有更长的生命周期。它可以是已经转换为 rvalue-reference (比如std::move(var)
)的命名变量,在这种情况下,不是从{{1}移动移动您仍在使用移动源维护共享所有权,如果目标shared_ptr
的范围较小,则不必要地延长指向对象的生命周期。
答案 1 :(得分: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";
}
使用clang / libc ++并在-std = c ++ 03中打印出来:
195.368µs
切换到-std = c ++ 11我得到:
16.422µs
您的里程可能会有所不同。
答案 2 :(得分:11)
最好使用move
:它应该比副本更有效,因为它不需要额外的原子增量和引用计数的减少。