42
更更更 C++: Move 更更更更更更更更更 Pete Isensee 更更更更更更 更更

更快的 C ++: Move 构造函数和完美转发

Embed Size (px)

DESCRIPTION

更快的 C ++: Move 构造函数和完美转发. Pete Isensee 高级技术小组 微软. 问题陈述. 对对象的深拷贝是昂贵的 C++ 是建立在复制语义的基础之上的 STL 容器是以存值方式来进行储存 编译器临时对象是通过值拷贝来进行复制的 拷贝在源代码中常常是不明显的 游戏拷贝对象 – 很多!. 示例. 深拷贝. struct Texture { unsigned long mSize ; unsigned long* mpBits ; };. 浅拷贝. 更深的拷贝. struct Particle { - PowerPoint PPT Presentation

Citation preview

Page 1: 更快的 C ++: Move 构造函数和完美转发

更快的 C++:Move 构造函数和完美转发

Pete Isensee高级技术小组微软

Page 2: 更快的 C ++: Move 构造函数和完美转发

问题陈述

• 对对象的深拷贝是昂贵的• C++ 是建立在复制语义的基础之上的– STL 容器是以存值方式来进行储存– 编译器临时对象是通过值拷贝来进行复制的

• 拷贝在源代码中常常是不明显的• 游戏拷贝对象 – 很多!

Page 3: 更快的 C ++: Move 构造函数和完美转发

示例

struct ParticleSystem { std::vector< Particle > mPar; Texture mTex;};

更深的拷贝

ParticleSystem particleSys(...); particleSys = StartExplosion(); // Explosion beginsparticleSys += AddSmoke(); // More particles added

struct Texture { unsigned long mSize; unsigned long* mpBits;};

深拷贝

浅拷贝struct Particle { Vector3 mPos; Vector3 mVel; Color mCol;};

Page 4: 更快的 C ++: Move 构造函数和完美转发

ParticleSystem particleSys(...); particleSys = StartExplosion(); // Explosion beginsparticleSys += AddSmoke(); // More particles added

Page 5: 更快的 C ++: Move 构造函数和完美转发

ParticleSystem particleSys(...);particleSys = StartExplosion(); // Explosion beginsparticleSys += AddSmoke(); // More particles added

粒子系统 开始爆炸 ()

tv

t

v

t

v

t

v

tv

t

v

tv

t

v

Page 6: 更快的 C ++: Move 构造函数和完美转发

复制临时对象是昂贵的

1 10 100 1000 10000 100000 10000001

10

100

1000

10000

100000

1000000

粒子

时间( Ticks)

粒子复制

( ticks )1 6,113

10 7,201100 5,543

1,000 8,57910,000 56,614

100,000 635,9621,000,000 6,220,013

运算符性能 = (常量 粒子系统类)

Page 7: 更快的 C ++: Move 构造函数和完美转发

避开临时对象是困难的bool Connect( const std::string& server, ... );if( Connect( “microsoft.com” ) ) // temporary object created

v.push_back( X(...) ); // another temporary

a = b + c; // b + c is a temporary object

a = b + c + d; // c+d is a temporary object // b+(c+d) is another temporary object

x++; // returns a temporary object

Page 8: 更快的 C ++: Move 构造函数和完美转发

我们所希望的是…

• 这样的一种编译环境…– 我们可以避免不必要的复制– 在此种情况下避免复制是安全的– 完全由程序员进行控制

• 比如…

Page 9: 更快的 C ++: Move 构造函数和完美转发

ParticleSystem particleSys(...);particleSys = StartExplosion(); // Explosion beginsparticleSys += AddSmoke(); // More particles added

粒子系统 开始爆炸 ()

tv

tv

t

v

tv

tv

t

v

tv

…t

v

Page 10: 更快的 C ++: Move 构造函数和完美转发

考虑赋值 struct ParticleSystem { std::vector< Particle > mPar; Texture mTex;};

ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Vector assignment (copy) mTex = rhs.mTex; // Texture assignment (copy) } return *this;}

典型的拷贝赋值

ParticleSystem& operator=( <Magic Type> rhs ) { // move semantics here ... return *this;}

我们所想要的

Page 11: 更快的 C ++: Move 构造函数和完美转发

解决方案:使用 C++11 标准来解决• 在不需要的时候不要进行复制,相反的使

用 move 语义来代替• 深对象是极其重要的• 主要的新语言特性:右值引用• 启用 move 语义,包括– Move 构造– Move 赋值– 完美转发

Page 12: 更快的 C ++: Move 构造函数和完美转发

示例 ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Particle vector copy mTex = rhs.mTex; // Texture copy } return *this;}

拷贝赋值

ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move mTex = std::move( rhs.mTex ); // Texture move } return *this; }

Move 赋值

Page 13: 更快的 C ++: Move 构造函数和完美转发

可移动对象:右值• Move 来自 = 自我抽取• 每一个表达不是左值就是右值• 从一个右值进行移动总是安全的

左值 右值

在内存中 是 否

可以获取地址 是 否

有名字 是 否

可移动 否 * 是

Page 14: 更快的 C ++: Move 构造函数和完美转发

左值与右值示例

X x; // x is an lvalue

X(); // X() is an rvalue

int a; // a is an lvalue

int a = 1+2; // a is an lvalue; 1+2 is an rvalue

foo( x ); // x is an lvalue

foo( bar() ); // bar() is an rvalue

++x; // lvalue

“abc” // lvalue

*ptr // lvalue

4321 // rvalue

x+42 // rvalue

x++; // rvalue

std::string( “abc” ) // rvalue

Page 15: 更快的 C ++: Move 构造函数和完美转发

右值引用

• T&: 引用(前 C++11 )• T&: C++11 中的左值引用• T&&: 右值引用; C++11 中的新内容• 右值引用所指向的对象可以安全地使用

move 语义• 右值引用绑定至右值表达• 左值引用绑定至左值表达

T&&

Page 16: 更快的 C ++: Move 构造函数和完美转发

绑定foo( ParticleSystem&& ); // A: rvaluefoo( const ParticleSystem&& ); // B: const rvaluefoo( ParticleSystem& ); // C: lvaluefoo( const ParticleSystem& ); // D: const lvalue

ParticleSystem particleSys;const ParticleSystem cparticleSys;

foo( particleSys ); // lvaluefoo( StartExplosion() ); // rvaluefoo( cparticleSys ); // const lvalue

Page 17: 更快的 C ++: Move 构造函数和完美转发

绑定和重载解析规则表达

引用类型

右值 常量右值const rvalue

左值 常量左值const ivalue

优先级

T&& 是 最高

const T&& 是 是

T& 是

const T& 是 是 是 是 最低

Page 18: 更快的 C ++: Move 构造函数和完美转发

std::move

• std::move ~= static_cast< T&& >(t)• 这等于告诉编译器:将该命名变量作为右值• 由于引用崩溃、参数演绎和其他晦涩难懂的语言规则使得

该函数的实现高度复杂

template< class T > inline typename std::remove_reference<T>::type&&move( T&& t ) noexcept { using ReturnType = typename std::remove_reference<T>::type&&; return static_cast< ReturnType >( t );}

ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move assignment mTex = std::move( rhs.mTex ); // Texture move assignment } return *this; }

Page 19: 更快的 C ++: Move 构造函数和完美转发

Move 赋值ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move assignment mTex = std::move( rhs.mTex ); // Texture move assignment } return *this; }

std::vector<T>& operator=( std::vector<T>&& rhs ) { if( this != &rhs ) { DestroyRange( mpFirst, mpLast ); // call all dtors if( mpFirst != nullptr ) free( mpFirst ); mpFirst = rhs.mpFirst; // eviscerate mpLast = rhs.mpLast; mpEnd = rhs.mpEnd; // rhs now empty shell rhs.mpFirst = rhs.mpLast = rhs.mpEnd = nullptr; } return *this; }

// Standard assignment operatorTexture& Texture::operator=( const Texture& rhs ) { if( this != &rhs ) { if( mpBits != nullptr) free( mpBits ); mSize = rhs.mSize; mpBits = malloc( mSize ); memcpy( mpBits, rhs.mpBits, mSize ); } return *this; }

Texture& Texture::operator=( Texture&& rhs ) { if( this != &rhs ) { if( mpBits != nullptr ) free( mpBits ); mpBits = rhs.mpBits; // eviscerate mSize = rhs.mSize; rhs.mpBits = nullptr; // clear rhs } return *this; }

Page 20: 更快的 C ++: Move 构造函数和完美转发

中场回顾• 使用右值引用语义来启动 moves• 使用非常量右值:重载运算符右边操作数 rhs• std::move 函数告诉编译器:“这是一个真正

的右值。”• 绑定规则允许逐步转换– 当你进行运算时实现右值引用– 从低级程序库开始– 或者从高层级的代码开始,由你自行决定

Page 21: 更快的 C ++: Move 构造函数和完美转发

重新观察运算性能

粒子复制

(ticks)Move (ticks)

1 6,113 101910 7,201 1100

100 5,543 9681,000 8,579 1200

10,000 56,614 865100,000 635,962 993

1,000,000 6,220,013 1173

1 10 100 1000 10000 100000 10000001

10

100

1000

10000

100000

1000000

粒子

时间( Ticks)

运算符 = (常量 粒子系统类)

运算符 = (粒子系统类)

Page 22: 更快的 C ++: Move 构造函数和完美转发

Move 构造函数ParticleSystem::ParticleSystem( ParticleSystem&& rhs ) : // invoke member move ctors mPar( std::move( rhs.mPar ) ), mTex( std::move( rhs.mTex ) ) {} vector<T>::vector( vector<T>&& rhs ) :

mpFirst( rhs.mpFirst ), // eviscerate mpLast ( rhs.mpLast ), mpEnd ( rhs.mpEnd ){ // rhs now an empty shell rhs.mpFirst = rhs.mpLast = rhs.mpEnd = nullptr; }

Texture::Texture( Texture&& rhs ) : mpBits( rhs.mpBits ), // eviscerate mSize( rhs.mSize ){ // rhs now an empty shell rhs.mpBits = nullptr; }

Page 23: 更快的 C ++: Move 构造函数和完美转发

完美转发问题假设我们有一些 setter 函数

void ParticleSystem::SetTexture( const Texture& texture ) { mTex = texture; // We’d like to move if tx is a temporary}

void ParticleSystem::SetTexture( Texture&& texture ) { mTex = std::move( texture ); // Move}

void ParticleSystem::Set( const A& a, const B& b ) { // Uh-oh, we need three new overloads...}

Page 24: 更快的 C ++: Move 构造函数和完美转发

使用函数模板及右值来进行解决C++11 中强大的新规则。鉴于:

模板右值引用参数可绑定到任意值template< typename T > void f( T&& t ); // template function

表达引用类型 右值 常量右值 左值 常量左值 优先级

template T&& 是 是 是 是 最高T&& 是const T&& 是 是T& 是const T& 是 是 是 是 最低

Page 25: 更快的 C ++: Move 构造函数和完美转发

绑定右值引用模板参数示例

template< typename T > void f( T&& t ); // template function

int a;const int ca = 42;

f( a ); // instantiates f( int& );f( ca ); // instantiates f( const int& );f( StartExplosion() ); // instantiates f( ParticleSystem&& );

Page 26: 更快的 C ++: Move 构造函数和完美转发

完美转发template< typename T >void ParticleSystem::SetTexture( T&& texture ) { mTex = std::forward<T>( texture ); // invokes right overload}

template< class T > inline T&& // typical std::forward implementationforward( typename identity<T>::type& t ) noexcept { return static_cast<T&&>( t );}

std::forward<T> 相当于– static_cast<[const] T&&>(t) 当 t 是一个右值– static_cast<[const] T&>(t) 当 t 是一个左值

Page 27: 更快的 C ++: Move 构造函数和完美转发

完美的构造函数ParticleSystem::ParticleSystem( const std::vector<Particle>& par, const Texture& texture ) : mPar( par ), mTex( texture ) {}

典型的多参数构造函数;不处理右值

template< typename V, typename T >ParticleSystem::ParticleSystem( V&& par, T&& texture ) : mPar( std::forward<V>( par ) ), mTex( std::forward<T>( texture ) ) {}

完美的构造函数;处理你往其中扔进的一切代码

Page 28: 更快的 C ++: Move 构造函数和完美转发

特殊的隐式成员函数

• 三法则( Rule of Three ):如果你定义了三个成员函数的任意一个,你必须同时定义其他两个

• Move二法则( Rule of Two Moves ):如果你定义了任意一个 move 函数,你必须同时定义另一个

函数 隐式产生的情况Default ctor 没有其他的构造函数显式声明Copy ctor 没有 move 构造函数或 move 赋值显示声明Copy assign 没有 move 构造函数或 move 赋值显示声明Move ctor 没有拷贝构造函数、 move 赋值或析构函数显示声明Move assign 没有拷贝构造函数、拷贝赋值或析构函数显示声明Dtor 没有析构函数显示声明

Page 29: 更快的 C ++: Move 构造函数和完美转发

明确特殊隐式函数struct ParticleSystem { std::vector< Particle > mPar; // Copyable/movable object Texture mTex; // Copyable/movable object

// Ctors ParticleSystem() = delete; ParticleSystem( const ParticleSystem& ) = default; ParticleSystem( ParticleSystem&& ) = default; // Assign ParticleSystem& operator=( const ParticleSystem& ) = default; ParticleSystem& operator=( ParticleSystem&& ) = default; // Destruction ~ParticleSystem() = default; };

Page 30: 更快的 C ++: Move 构造函数和完美转发

C++11• STL 容器 move 启用– 包括 std::string

• STL 算法 move 启用– 包括排序、分区、交换

• 只要通过简单的重新编译你就可以立即获得速度优势

template< typename T >swap( T& a, T& b ) { T tmp( std::move( a ) ); a = std::move( b ); b = std::move( tmp );}

Page 31: 更快的 C ++: Move 构造函数和完美转发

推荐用语 : 可移动类型struct Deep { Deep( const Deep& ); // Copy ctor Deep( Deep&& ); // Move ctor template< typename A, typename B > Deep( A&&, B&& ); // Perfect forwarding ctor

Deep& operator=( const Deep& ); // Copy assignment Deep& operator=( Deep&& ); // Move assignment ~Deep();

template< typename A > // Deep setters void SetA( A&& );};

Page 32: 更快的 C ++: Move 构造函数和完美转发

推荐用语:空指针T( T&& rhs ) : ptr( rhs.ptr ) // eviscerate{ rhs.ptr = nullptr; // rhs: safe state}

T& operator=( T&& rhs ) { if( this != &rhs ) { if( ptr != nullptr ) free( ptr ); ptr = rhs.ptr; // eviscerate rhs.ptr = nullptr; // rhs: safe state } return *this;}

Move 构造函数

Move 赋值

Page 33: 更快的 C ++: Move 构造函数和完美转发

推荐用语:高级 ObjsT( T&& rhs ) : base( std::move( rhs ) ), // base m ( std::move( rhs.m ) ) // members{}

T& operator=( T&& rhs ) { if( this != &rhs ) { m = std::move( rhs.m ); // eviscerate } return *this;}

Move 构造函数

Move 赋值

Page 34: 更快的 C ++: Move 构造函数和完美转发

推荐用法:完美转发template< typename A, typename B >T( A&& a, B&& b ) : // binds to any 2 params ma( std::forward<A>( a ) ), mb( std::forward<B>( b ) ){}

template< typename A >void SetA( A&& a ) // binds to anything{ ma = std::forward<A>( a );}

构造函数

Setter 函数

Page 35: 更快的 C ++: Move 构造函数和完美转发

编译器和 move支持特征 微软 GCC Intel Clang

右值引用 VS 2010 4.3 11.1 2.9

STL move 语义 VS 2010 4.3 11.1 2.9

空指针标识 nullptr VS 2010 4.6 12.1 2.9

可变参数模板 4.3 12.1 2.9

Defaulted 和 deleted 函数 4.4 12.0 3.0

noexcept 4.6 3.0

详尽清单: http://wiki.apache.org/stdcxx/C++0xCompilerSupport

Page 36: 更快的 C ++: Move 构造函数和完美转发

进阶要点

• 通过重载右值引用,你可以在编译时选择是否跳转至 x 可移动的情况( x 为临时对象)

• 你可以逐步地实现超载• 好处会累积至深对象• 可显著提高性能

Page 37: 更快的 C ++: Move 构造函数和完美转发

更进一步的研究:一些本次演讲所未涵盖的主题

• x 值 xvalues 、泛左值 glvalues 、纯右值prvalues

• 安置(例如“位置插入”)– 使用容器创建元素, w/ no moves/copies– 使用完美转发和可变参数函数

• 在其他情况下移动左值是 OK 的• Moves 和例外情况• 完美转发并不总是那么完美– 例如积分和指针类型;还有位域

• Noexcept 和隐式 move

Page 38: 更快的 C ++: Move 构造函数和完美转发

最佳的做法• 更新至支持右值引用的编译器• 现在返回值是合理的了 – 既是可读的又是快速的• 对深对象添加 move 构造函数 / 赋值 /setters 函数• Move 用语: this 指针 = 右指针 rhs pointers = null• 使用非常量右值引用• 进行移动时,满足调用的 obj 不变量• 避免返回常量 T – 禁止move 语义• 明确特殊隐式函数• 通过执行新的 move 代码以确保正确性

Page 39: 更快的 C ++: Move 构造函数和完美转发

谢谢!• 我的联系方式 : [email protected] • 个人主页 : http://www.tantalon.com/pete.htm • Scott Meyers: http://www.aristeia.com • Stephan Lavavej: http://blogs.msdn.com • Dave Abrahams: http://cpp-next.com • Thomas Becker: http://thbecker.net• Marc Gregoire: http://www.nuonsoft.com

• 请让我知道你使用 move 语义启用你的代码后你所观察到的是什么样的结果

Page 40: 更快的 C ++: Move 构造函数和完美转发

其他参考材料

Page 41: 更快的 C ++: Move 构造函数和完美转发

C++ 标准参考文献• N1610 (v0.1) 2004

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html • N2118 (v1.0) 2006

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html • N2844 (v2.0) 2009 (VC10 impl)

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2844.html • N3310 (sections 840, 847, 858) (v2.1) 2011 (VC11 impl)

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html • N3053 (v3.0) 2010

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html

Page 42: 更快的 C ++: Move 构造函数和完美转发

右值参考文献• Scott Meyers 的 Move 语义和右值引用:

http://www.aristeia.com/TalkNotes/ACCU2011_MoveSemantics.pdf 以及 http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references

• Scott Meyers 对完美转发的研究( C++ 及 2011后版本)• Thomas Becker 对右值引用的解释: http

://thbecker.net/articles/rvalue_references/section_01.html • STL博客 :

http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx?PageIndex=3

• Marc Gregoire 的博客 http://www.nuonsoft.com/blog/2009/06/07/the-move-constructor-in-visual-c-2010/

• Visual Studio C++ 11 的新特性 http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx

• Mikael Kilpelainen 的左值和右值 http://accu.org/index.php/journals/227• 从左值进行移动 http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references • 二进制运算符 http://cpp-next.com/archive/2009/09/making-your-next-move/ • 安置 http://stackoverflow.com/questions/4303513/push-back-vs-emplace-back