Upload
globallogic-ukraine
View
900
Download
1
Tags:
Embed Size (px)
DESCRIPTION
В своей презентации “С++ 11 usage experience: Move semantic”, Дмитрий Гурин, bp GlobalLogic, рассказывает о части нововведений нового стандарта C++, особенностях работы с объектами rvalue и lvalue, а также особенностями использования функций std::move и std::forward. В конце доклада — полезные советы по оптимизации кода с применением возможностей нового стандарта С++. Напомним, Embedded Kyiv TechTalk состоялся 14 декабря 2013 года.
Citation preview
Move semantics
Introduction: copying temporaries
pitfall
Moving constructors
Perfect forwarding
Extending horizons: tips & tricks
Q&A
Find an error and propose a fix:
vector<string>& getNames() { vector<string> names; names.push_back("Ivan"); names.push_back("Artem"); return names; }
Possible solutions:
1
void getNames(vector<string>& names) { names.push_back("Ivan"); names.push_back("Artem"); }
vector<string> names; getNames(names);
2
auto_ptr<vector<string> > getNames() { auto_ptr<vector<string> > names( new vector<string>); names->push_back("Ivan"); names->push_back("Artem"); return names; }
auto_ptr<vector<string> > names( getNames());
3
vector<string> getNames() { vector<string> names; names.push_back("Ivan"); names.push_back("Artem"); return names; }
vector<string> const& names = getNames();
Compiler:
• Return Value Optimization (RVO);
• Named Return Value Optimization (NRVO).
Techniques:
• Copy-on-write (COW);
• “Move Constructors”:
Simulate temporary objects and treat them
differently.
Language:
• Recognize temporary objects;
• Treat temporary objects differently.
expression
glvalue (“generalized”
lvalue)
rvalue
lvalue prvalue
(“pure” rvalue)
xvalue (“eXpiring”
value)
lvalue • identifies a non-temporary object.
prvalue • “pure rvalue” - identifies a temporary object or is a
value not associated with any object.
xvalue • either temporary object or non-temporary object at
the end of its scope.
glvalue • either lvalue or xvalue.
rvalue • either prvalue or xvalue.
class Buffer { char* _data; size_t _size; // ... // copy the content from lvalue Buffer(Buffer const& lvalue) : _data(nullptr), _size(0) { // perform deep copy ... } // take the internals from rvalue Buffer(Buffer&& rvalue) : _data(nullptr), _size(0) { swap(_size, rvalue._size); swap(_data, rvalue._data); // it is vital to keep rvalue // in a consistent state } // ...
Buffer getData() { /*...*/ } // ... Buffer someData; // ... initialize someData Buffer someDataCopy(someData); // make a copy Buffer anotherData(getData()); // move temporary
class Configuration { // ... Configuration( Configuration const&); Configuration const& operator=( Configuration const& ); Configuration(Configuration&& ); Configuration const& operator=( Configuration&& ); // ... }; class ConfigurationStorage; class ConfigurationManger { // ... Configuration _current; // ... bool reload(ConfigurationStorage& ); // ... };
bool ConfigurationManager::reload( ConfigurationStorage& source ) { Configuration candidate = source.retrieve(); // OK, call move ctor if (!isValid(candidate)) { return false; } _current = candidate; // BAD, copy lvalue to lvalue _current = move(candidate); // OK, use move assignment return true; }
Does not really move anything;
Does cast a reference to object to
xvalue:
Does not produce code for run-time.
template<typename _T> typename remove_reference<_T>::type&& move(_T&& t) { return static_cast<typename remove_reference<_T>::type&&>(t); }
Move is an optimization for copy.
Copy of rvalue objects may become
move.
Explicit move for objects without move
support becomes copy.
Explicit move objects of built-in types
always becomes copy.
Compiler does generate move constructor/assignment operator when:
• there is no user-defined copy or move operations (including default);
• there is no user-defined destructor (including default);
• all non-static members and base classes are movable.
Implicit move constructor/assignment operator
does perform member-wise moving. Force-generated move operators does perform
member-wise moving: • dangerous when dealing with raw resources (memory, handles,
etc.).
class Configuration { // have copy & move operations implemented // ... }; class ConfigurationManger { // ... Configuration _current; // ... ConfigurationManager(Configuration const& defaultConfig) : _current(defaultConfig) // call copy ctor { } ConfigurationManager(Configuration&& defaultConfig) // : _current(defaultConfig) // BAD, call copy ctor : _current(move(defaultConfig)) // OK, call move ctor { } // ... };
class Configuration { // have copy & move operations implemented // ... }; class ConfigurationManger { // ... Configuration _current; // ... template <class _ConfigurationRef> explicit ConfigurationManager(_ConfigurationRef&& defaultConfig) : _current(forward<_ConfigurationRef>(defaultConfig)) // OK, deal with either rvalue or lvalue { } // ... };
Normally C++ does not allow reference to
reference types.
Template types deduction often does
produce such types.
There are rules for deduct a final type then:
• T& & T&
• T& && T&
• T&& & T&
• T&& && T&&
Just a cast as well;
Applicable only for function templates;
Preserves arguments
lvaluesness/rvaluesness/constness/etc.
template<typename _T> _T&& forward(typename remove_reference<_T>::type& t) { return static_cast<_T&&>(t); } template<typename _T> _T&& forward(typename remove_reference<_T>::type&& t) { return static_cast<_T&&>(t); }
class ConfigurationManger { template <class _ConfigurationType> ConfigurationManager(_ConfigurationType&& defaultConfig) : _current(forward<_ConfigurationType>(defaultConfig)) {} // ... }; // ... Configuration defaultConfig; ConfigurationManager configMan(defaultConfig); // _ConfigurationType => Configuration& // decltype(defaultConfig) => Configuration& && => Configuration& // forward => Configuration& && forward<Configuration&>(Configuration& ) // => Configuration& forward<Configuration&>(Configuration& ) ConfigurationManager configMan(move(defaultConfig)); // _ConfigurationType => Configuration // decltype(defaultConfig) => Configuration&& // forward => Configuration&& forward<Configuration>(Configuration&& )
Passing parameters to functions:
class ConfigurationStorage { std::string _path; // ... template <class _String> bool open(_String&& path) { _path = forward<_String>(path); // ... } }; // ... string path("somewhere_near_by"); ConfigurationStorage storage; storage.open(path); // OK, pass as string const& storage.open(move(path)); // OK, pass as string&& storage.open("far_far_away"); // OK, pass as char const*
Passing parameters by value:
class ConfigurationManger { Configuration _current; // ... bool setup( Configuration const& newConfig) { _current = newConfig; // make a copy } bool setup( Configuration&& newConfig) { _current = move(newConfig); // simply take it } }; Configuration config; configManager.setup(config); // pass by const& configManager.setup(move(config)); // pass by &&
class ConfigurationManger { Configuration _current; // ... bool setup( Configuration newConfig) { // ... _current = move(newConfig); // could just take it } }; Configuration config; configManager.setup(config); // make copy ahead configManager.setup(move(config)); // use move ctor
Return results:
vector<string> getNamesBest() { vector<string> names; // ... return names; // OK, compiler choice for either of move ctor or RVO } vector<string> getNamesGood() { vector<string> names; // ... return move(names); // OK, explicit call move ctor } vector<string>&& getNamesBad() { vector<string> names; // ... return move(names); // BAD, return a reference to a local object }
Extending object life-time:
vector<string> getNames() { vector<string> names; // ... return names; } // ... vector<string> const& names = getNames(); // OK, both C++03/C++11 vector<string>&& names = getNames(); // OK, C++11: extends life-time vector<string> names = getNames(); // OK, C++11: move construction vector<string>& names = getNames(); // BAD, dangling reference here vector<string>&& names = move(getNames());// BAD, dangling reference here
The hardest work for move semantic
support usually performed by the compiler
and the Standard Library.
There is still a lot of space for apply move
semantic explicitly for performance critical
parts of the application: • It is up to developer to ensure the code correctness
when the move semantic used explicitly.
• The best way to add move semantic support to own
class is using pImpl idiom.