C++ 11 usage experience

24
Move semantics

description

В своей презентации “С++ 11 usage experience: Move semantic”, Дмитрий Гурин, bp GlobalLogic, рассказывает о части нововведений нового стандарта C++, особенностях работы с объектами rvalue и lvalue, а также особенностями использования функций std::move и std::forward. В конце доклада — полезные советы по оптимизации кода с применением возможностей нового стандарта С++. Напомним, Embedded Kyiv TechTalk состоялся 14 декабря 2013 года.

Transcript of C++ 11 usage experience

Page 1: C++ 11 usage experience

Move semantics

Page 2: C++ 11 usage experience

Introduction: copying temporaries

pitfall

Moving constructors

Perfect forwarding

Extending horizons: tips & tricks

Q&A

Page 3: C++ 11 usage experience

Find an error and propose a fix:

vector<string>& getNames() { vector<string> names; names.push_back("Ivan"); names.push_back("Artem"); return names; }

Page 4: C++ 11 usage experience

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

Page 5: C++ 11 usage experience

Compiler:

• Return Value Optimization (RVO);

• Named Return Value Optimization (NRVO).

Techniques:

• Copy-on-write (COW);

• “Move Constructors”:

Simulate temporary objects and treat them

differently.

Page 6: C++ 11 usage experience

Language:

• Recognize temporary objects;

• Treat temporary objects differently.

Page 7: C++ 11 usage experience

expression

glvalue (“generalized”

lvalue)

rvalue

lvalue prvalue

(“pure” rvalue)

xvalue (“eXpiring”

value)

Page 8: C++ 11 usage experience

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.

Page 9: C++ 11 usage experience

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

Page 10: C++ 11 usage experience

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; }

Page 11: C++ 11 usage experience

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); }

Page 12: C++ 11 usage experience

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.

Page 13: C++ 11 usage experience

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

Page 14: C++ 11 usage experience

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 { } // ... };

Page 15: C++ 11 usage experience

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 { } // ... };

Page 16: C++ 11 usage experience

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&&

Page 17: C++ 11 usage experience

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); }

Page 18: C++ 11 usage experience

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&& )

Page 19: C++ 11 usage experience

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*

Page 20: C++ 11 usage experience

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

Page 21: C++ 11 usage experience

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 }

Page 22: C++ 11 usage experience

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

Page 23: C++ 11 usage experience

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.

Page 24: C++ 11 usage experience