1 / 42

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

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

Download Presentation

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

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 更快的C++:Move构造函数和完美转发 Pete Isensee 高级技术小组 微软

  2. 问题陈述 • 对对象的深拷贝是昂贵的 • C++是建立在复制语义的基础之上的 • STL容器是以存值方式来进行储存 • 编译器临时对象是通过值拷贝来进行复制的 • 拷贝在源代码中常常是不明显的 • 游戏拷贝对象 – 很多!

  3. 示例 深拷贝 struct Texture { unsigned long mSize; unsigned long* mpBits; }; 浅拷贝 更深的拷贝 struct Particle { Vector3 mPos; Vector3 mVel; Color mCol; }; structParticleSystem { std::vector< Particle > mPar; Texture mTex; }; ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

  4. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

  5. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added 粒子系统 开始爆炸() v v v v v v … … … … … t … … t t t t t v t v t …

  6. 复制临时对象是昂贵的 运算符性能=(常量粒子系统类)

  7. 避开临时对象是困难的 bool Connect( conststd::string& server, ... ); if( Connect( “microsoft.com” ) ) // temporary object created v.push_back( X(...) ); // another temporary a = b + c; // b + c is a temporary object x++; // returns a temporary object a = b + c + d; // c+d is a temporary object // b+(c+d) is another temporary object

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

  9. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added 粒子系统 开始爆炸() v v v v … … … t … t t t v v t t … v v t t

  10. 考虑赋值 structParticleSystem { std::vector< Particle > mPar; Texture mTex; }; 典型的拷贝赋值 我们所想要的 ParticleSystem& operator=( constParticleSystem& 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; }

  11. 解决方案:使用C++11标准来解决 • 在不需要的时候不要进行复制,相反的使用move语义来代替 • 深对象是极其重要的 • 主要的新语言特性:右值引用 • 启用move语义,包括 • Move构造 • Move赋值 • 完美转发

  12. 示例 拷贝赋值 Move赋值 ParticleSystem& operator=( constParticleSystem& 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; }

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

  14. 左值与右值示例 ++x; // lvalue int a; // a is an lvalue X x; // x is an lvalue x++; // rvalue X(); // X() is an rvalue *ptr// lvalue int a = 1+2; // a is an lvalue; 1+2 is an rvalue foo( x ); // x is an lvalue x+42 // rvalue “abc” // lvalue foo( bar() ); // bar() is an rvalue 4321 // rvalue std::string( “abc” ) // rvalue

  15. 右值引用 T&& • T&: 引用(前C++11) • T&: C++11中的左值引用 • T&&: 右值引用;C++11中的新内容 • 右值引用所指向的对象可以安全地使用move语义 • 右值引用绑定至右值表达 • 左值引用绑定至左值表达

  16. 绑定 foo( ParticleSystem&& ); // A: rvalue foo( constParticleSystem&& ); // B: constrvalue foo( ParticleSystem& ); // C: lvalue foo( constParticleSystem& ); // D: constlvalue ParticleSystemparticleSys; constParticleSystemcparticleSys; foo( particleSys ); // lvalue foo( StartExplosion() ); // rvalue foo( cparticleSys ); // constlvalue

  17. 绑定和重载解析规则

  18. std::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::move ~= static_cast< T&& >(t) • 这等于告诉编译器:将该命名变量作为右值 • 由于引用崩溃、参数演绎和其他晦涩难懂的语言规则使得该函数的实现高度复杂 template< class T > inline typenamestd::remove_reference<T>::type&& move( T&& t ) noexcept { using ReturnType = typenamestd::remove_reference<T>::type&&; return static_cast< ReturnType >( t ); }

  19. 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 operator Texture& 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; }

  20. 中场回顾 • 使用右值引用语义来启动moves • 使用非常量右值:重载运算符右边操作数rhs • std::move函数告诉编译器:“这是一个真正的右值。” • 绑定规则允许逐步转换 • 当你进行运算时实现右值引用 • 从低级程序库开始 • 或者从高层级的代码开始,由你自行决定

  21. 重新观察运算性能 运算符=(常量粒子系统类) 运算符=(粒子系统类)

  22. Move构造函数 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; } ParticleSystem::ParticleSystem( ParticleSystem&& rhs ) : // invoke member move ctors mPar( std::move( rhs.mPar ) ), mTex( std::move( rhs.mTex ) ) { }

  23. 完美转发问题 假设我们有一些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( constA& a, const B& b ) { // Uh-oh, we need three new overloads... }

  24. 使用函数模板及右值来进行解决 C++11中强大的新规则。鉴于: 模板右值引用参数可绑定到任意值 template< typename T > void f( T&& t ); // template function

  25. 绑定右值引用模板参数 示例 template< typename T > void f( T&& t ); // template function int a; constintca = 42; f( a ); // instantiates f( int& ); f( ca ); // instantiates f( constint& ); f( StartExplosion() ); // instantiates f( ParticleSystem&& );

  26. 完美转发 template< typename T > void ParticleSystem::SetTexture( T&& texture ) { mTex = std::forward<T>( texture ); // invokes right overload } std::forward<T> 相当于 • static_cast<[const] T&&>(t) 当 t 是一个右值 • static_cast<[const] T&>(t) 当 t 是一个左值 template< class T > inline T&& // typical std::forward implementation forward( typename identity<T>::type& t ) noexcept { return static_cast<T&&>( t ); }

  27. 完美的构造函数 典型的多参数构造函数;不处理右值 ParticleSystem::ParticleSystem( conststd::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 ) ) { }

  28. 特殊的隐式成员函数 • 三法则(Rule of Three):如果你定义了三个成员函数的任意一个,你必须同时定义其他两个 • Move二法则(Rule of Two Moves):如果你定义了任意一个move函数,你必须同时定义另一个

  29. 明确特殊隐式函数 structParticleSystem { std::vector< Particle > mPar; // Copyable/movable object Texture mTex; // Copyable/movable object // Ctors ParticleSystem() = delete; ParticleSystem( constParticleSystem& ) = default; ParticleSystem( ParticleSystem&& ) = default; // Assign ParticleSystem& operator=( constParticleSystem& ) = default; ParticleSystem& operator=( ParticleSystem&& ) = default; // Destruction ~ParticleSystem() = default; };

  30. C++11 template< typename T > swap( T& a, T& b ) { T tmp( std::move( a ) ); a = std::move( b ); b = std::move( tmp ); } • STL容器move启用 • 包括std::string • STL算法move启用 • 包括排序、分区、交换 • 只要通过简单的重新编译你就可以立即获得速度优势

  31. 推荐用语: 可移动类型 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&& ); };

  32. 推荐用语:空指针 T( T&& rhs ) : ptr( rhs.ptr ) // eviscerate { rhs.ptr = nullptr; // rhs: safe state } Move构造函数 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赋值

  33. 推荐用语:高级Objs T( T&& rhs ) : base( std::move( rhs ) ), // base m ( std::move( rhs.m ) ) // members { } Move构造函数 T& operator=( T&& rhs ) { if( this != &rhs ) { m = std::move( rhs.m ); // eviscerate } return *this; } Move赋值

  34. 推荐用法:完美转发 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函数

  35. 编译器和move支持 详尽清单:http://wiki.apache.org/stdcxx/C++0xCompilerSupport

  36. 进阶要点 • 通过重载右值引用,你可以在编译时选择是否跳转至x可移动的情况(x为临时对象) • 你可以逐步地实现超载 • 好处会累积至深对象 • 可显著提高性能

  37. 更进一步的研究:一些本次演讲所未涵盖的主题更进一步的研究:一些本次演讲所未涵盖的主题 • x值xvalues、泛左值glvalues、纯右值prvalues • 安置(例如“位置插入”) • 使用容器创建元素,w/ no moves/copies • 使用完美转发和可变参数函数 • 在其他情况下移动左值是OK的 • Moves和例外情况 • 完美转发并不总是那么完美 • 例如积分和指针类型;还有位域 • Noexcept和隐式move

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

  39. 谢谢! • 我的联系方式: pkisensee@msn.com • 个人主页: 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语义启用你的代码后你所观察到的是什么样的结果

  40. 其他参考材料

  41. 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

  42. 右值参考文献 • 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 • MikaelKilpelainen的左值和右值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

More Related