|
Boost : |
Subject: [boost] rvalue ref best practices?
From: Daniel Larimer (dlarimer_at_[hidden])
Date: 2012-06-09 16:21:41
I am trying to define my library API and have been using rvalue references and have observed some patterns that I would like to run by the community.
In c++03 I would pass almost everything by const& unless it was to be modified. This results in extra copies any time a temporary is passed.
In c++11 I would pass anything that I plan on 'storing' or 'consuming' by &&. This results in the most efficient code, but 'breaks' lvalue compatibility and forces me to consider having two (or more) versions of every method. Unfortunately, you get a explosion of permutations of rvalue and const& as the number of arguments increases and it makes getting function pointers much more 'ugly'.
Instead of providing two (or more) overloads, one for lvalues and one for rvalue, I have decided to only provide the rvalue overload and if the user wants to pass an lvalue they wrap with std::ref(), std::cref(), std::move() or my own helper copy() which makes the copy explicit and converts the lvalue to a rvalue. (std::ref() and std::cref() could only work if the arguments are template parameters... in which case the method would probably use std::forward<>() to handle the auto-detection of move vs copy semantics.
template<typename T>
T copy( const T& v ) { return v; }
If I am writing a method that does not 'store' or 'consume' the input then I use const&
As rvalue references are quite new, this 'convention' may be unexpected by users. How would you feel about using an API that assumed move semantics unless you explicitly specify copy or reference semantics? Is there any reason that there shouldn't be a boost::copy() in boost/utility for use in this situation?
Are there some down sides to this approach that I am missing? In my experience 90% of functions that take std::string() end up using a temporary and 90% of strings are 'calculated', 'used', and 'forgotten', yet most methods take const& and result in a temporary value.
I should probably clarify, that I would use const& for any types that do not benefit from move semantics (no deep copies). But when you are writing template code, you do not want to make that assumption about the type.
Another practice I am considering is making certain classes 'explicit copy only'.
template<typename T>
T copy( const T& t ) { return t; }
class test {
public:
test(){...};
test( test&& t ){...}
private:
template<typename T>
friend T copy(const T&t);
test& operator=(const test&); // not implemented
test( const test& t ){...}
std::vector<char> large_data;
};
int main( int argc, char** argv ) {
test x;
test y = copy(x);
test z = x; // error, test(const test&) is private...
return 0;
}
I would use this for any class that contains any 'movable members', such a strings, vectors, maps, etc. Technically there is no reason why I couldn't copy them, but why shouldn't I attempt move semantics everywhere possible unless I really do want to copy? With implicit copies there is no way for the compiler to warn me that I am doing something potentially expensive.
If it didn't break backward compatibility I would suggest that all of the standard containers adopt an explicit copy or move syntax or at least have a compiler flag to 'enable/disable' implicit copying for 'testing purposes'. Perhaps we could put them in a different namespace for those of us that want to disable implicit copying of deep data types, ie: xc::vector<>, xc::string, etc.
Are these sane conventions? What could go wrong? I am I abusing rvalue references?
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk