From 062000ed68cecf59f29b99f5a6167529ea359979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Sat, 15 Feb 2014 23:04:15 +0100 Subject: [PATCH] Added BOOST_MOVE_RET --- doc/move.qbk | 216 +++++++++++++---------------- example/copymovable.hpp | 18 ++- example/doc_move_return.cpp | 70 ++++++++++ include/boost/move/core.hpp | 105 +++++++++++++- proj/vc7ide/Move.sln | 8 ++ proj/vc7ide/doc_move_return.vcproj | 134 ++++++++++++++++++ test/move.cpp | 74 +++++++++- 7 files changed, 496 insertions(+), 129 deletions(-) create mode 100644 example/doc_move_return.cpp create mode 100644 proj/vc7ide/doc_move_return.vcproj diff --git a/doc/move.qbk b/doc/move.qbk index 64328be..f7fcd29 100644 --- a/doc/move.qbk +++ b/doc/move.qbk @@ -7,7 +7,7 @@ [library Boost.Move [quickbook 1.5] [authors [Gaztanaga, Ion]] - [copyright 2008-2012 Ion Gaztanaga] + [copyright 2008-2014 Ion Gaztanaga] [id move] [dirname move] [purpose Move semantics] @@ -327,127 +327,92 @@ C++03 compilers. In compilers with rvalue references perfect forwarding is achie [endsect] -[/[section:perfect_forwarding Perfect Forwarding] - / - /Consider writing a generic factory function that returns a std::shared_ptr for a newly - /constructed generic type. Factory functions such as this are valuable for encapsulating - /and localizing the allocation of resources. Obviously, the factory function must accept - /exactly the same sets of arguments as the constructors of the type of objects constructed. - /Today this might be coded as: - / - /[c++] - / - / template - / std::shared_ptr - / factory() // no argument version - / { - / return std::shared_ptr(new T); - / } - / - / template - / std::shared_ptr - / factory(const A1& a1) // one argument version - / { - / return std::shared_ptr(new T(a1)); - / } - / - / // all the other versions - / - / - /In the interest of brevity, we will focus on just the one-parameter version. For example: - / - / [c++] - / - / std::shared_ptr p = factory(5); - / - / - / [*Question]: What if T's constructor takes a parameter by non-const reference? - / - / In that case, we get a compile-time error as the const-qualifed argument of the factory - / function will not bind to the non-const parameter of T's constructor. - / - / To solve that problem, we could use non-const parameters in our factory functions: - / - / [c++] - / - / template - / std::shared_ptr - / factory(A1& a1) - / { - / return std::shared_ptr(new T(a1)); - / } - / - / - / This is much better. If a const-qualified type is passed to the factory, the const will - / be deduced into the template parameter (A1 for example) and then properly forwarded to - / T's constructor. Similarly, if a non-const argument is given to factory, it will be - / correctly forwarded to T's constructor as a non-const. Indeed, this is precisely how - /forwarding applications are coded today (e.g. `std::bind`). - / - /However, consider: - / - /[c++] - / - / std::shared_ptr p = factory(5); // error - / A* q = new A(5); // ok - / - / - /This example worked with our first version of factory, but now it's broken: The "5" - /causesthe factory template argument to be deduced as int& and subsequently will not - /bind to the rvalue "5". Neither solution so far is right. Each breaks reasonable and - /common code. - / - /[*Question]: What about overloading on every combination of AI& and const AI&? - / - /This would allow use to handle all examples, but at a cost of an exponential explosion: - /For our two-parameter case, this would require 4 overloads. For a three-parameter factory - /we would need 8 additional overloads. For a four-parameter factory we would need 16, and - /so on. This is not a scalable solution. - / - /Rvalue references offer a simple, scalable solution to this problem: - / - /[c++] - / - / template - / std::shared_ptr - / factory(A1&& a1) - / { - / return std::shared_ptr(new T(std::forward(a1))); - / } - / - / Now rvalue arguments can bind to the factory parameters. If the argument is const, that - / fact gets deduced into the factory template parameter type. - / - / [*Question]: What is that forward function in our solution? - / - / Like move, forward is a simple standard library function used to express our intent - / directly and explicitly, rather than through potentially cryptic uses of references. - / We want to forward the argument a1, so we simply say so. - / - / Here, forward preserves the lvalue/rvalue-ness of the argument that was passed to factory. - / If an rvalue is passed to factory, then an rvalue will be passed to T's constructor with - / the help of the forward function. Similarly, if an lvalue is passed to factory, it is - / forwarded to T's constructor as an lvalue. - / - / The definition of forward looks like this: - / - / [c++] - / - / template - / struct identity - / { - / typedef T type; - / }; - / - / template - / T&& forward(typename identity::type&& a) - / { - / return a; - / } - / - /[endsect] - / - /] +[section:move_return Implicit Move when returning a local object] + +The C++ standard specifies situations where an implicit move operation is safe and the +compiler can use it in cases were the (Named) Return Value Optimization) can't be used. +The typical use case is when a function returns a named (non-temporary) object by value +and the following code will perfectly compile in C++11: + +[c++] + + //Even if movable can't be copied + //the compiler will call the move-constructor + //to generate the return value + // + //The compiler can also optimize out the move + //and directly construct the object 'm' + movable factory() + { + movable tmp; + m = ... + //(1) moved instead of copied + return tmp; + }; + + //Initialize object + movable m(factory()); + + +In compilers without rvalue references and some non-conforming compilers (such as Visual C++ 2010/2012) +the line marked with `(1)` would trigger a compilation error because `movable` can't be copied. Using a explicit +`::boost::move(tmp)` would workaround this limitation but it would code suboptimal in C++11 compilers +(as the compile could not use (N)RVO to optimize-away the copy/move). + +[*Boost.Move] offers an additional macro called [macroref BOOST_MOVE_RET BOOST_MOVE_RET] that can be used to +alleviate this problem obtaining portable move-on-return semantics. Let's use the previously presented +movable-only `movable` class with classes `copyable` (copy-only type), `copy_movable` (can be copied and moved) and +`non_copy_movable` (non-copyable and non-movable): + +[import ../example/copymovable.hpp] +[copy_movable_definition] + +and build a generic factory function that returns a newly constructed value or a reference to an already +constructed object. + +[import ../example/doc_move_return.cpp] +[move_return_example] + +[*Caution]: When using this macro in a non-conforming or C++03 +compilers, a move will be performed even if the C++11 standard does not allow it +(e.g. returning a static variable). The user is responsible for using this macro +only used to return local objects that met C++11 criteria. E.g.: + +[c++] + + struct foo + { + copy_movable operator()() const + { + //ERROR! The Standard does not allow implicit move returns when the object to be returned + //does not met the criteria for elision of a copy operation (such as returning a static member data) + //In C++03 compilers this will MOVE resources from cm + //In C++11 compilers this will COPY resources from cm + //so DON'T use use BOOST_MOVE_RET without care. + return BOOST_MOVE_RET(copy_movable, cm); + } + + static copy_movable cm; + }; + + +[*Note]: When returning a temporary object `BOOST_MOVE_REF` is not needed as copy ellision rules will work on +both C++03 and C++11 compilers. + +[c++] + + //Note: no BOOST_MOVE_RET + movable get_movable() + { return movable(); } + + copy_movable get_copy_movable() + { return copy_movable(); } + + copyable get_copyable() + { return copyable(); } + + +[endsect] [section:move_iterator Move iterators] @@ -790,6 +755,12 @@ Many thanks to all boosters that have tested, reviewed and improved the library. [section:release_notes Release Notes] +[section:release_notes_boost_1_56_00 Boost 1.56 Release] + +* Added [macroref BOOST_MOVE_RET BOOST_MOVE_RET]. + +[endsect] + [section:release_notes_boost_1_55_00 Boost 1.55 Release] * Fixed bugs [@https://svn.boost.org/trac/boost/ticket/7952 #7952], @@ -797,6 +768,7 @@ Many thanks to all boosters that have tested, reviewed and improved the library. [@https://svn.boost.org/trac/boost/ticket/8765 #8765], [@https://svn.boost.org/trac/boost/ticket/8842 #8842], [@https://svn.boost.org/trac/boost/ticket/8979 #8979]. + [endsect] diff --git a/example/copymovable.hpp b/example/copymovable.hpp index cb2c050..173d420 100644 --- a/example/copymovable.hpp +++ b/example/copymovable.hpp @@ -13,8 +13,8 @@ #include -//[movable_definition -//header file "copy_movable.hpp" +//[copy_movable_definition +//header file "copymovable.hpp" #include //A copy_movable class @@ -43,6 +43,20 @@ class copy_movable { return value_ == 0; } }; +//A copyable-only class +class copyable +{}; + +//A copyable-only class +class non_copy_movable +{ + public: + non_copy_movable(){} + private: + non_copy_movable(const non_copy_movable&); + non_copy_movable& operator=(const non_copy_movable&); +}; + //] #include diff --git a/example/doc_move_return.cpp b/example/doc_move_return.cpp new file mode 100644 index 0000000..0d5db0f --- /dev/null +++ b/example/doc_move_return.cpp @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/move for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#include + +//[move_return_example +#include "movable.hpp" +#include "copymovable.hpp" +#include + +template +struct factory_functor +{ + typedef Type return_type; + + Type operator()() const + { Type t; return BOOST_MOVE_RET(Type, t); } +}; + +struct return_reference +{ + typedef non_copy_movable &return_type; + + non_copy_movable &operator()() const + { return ncm; } + + static non_copy_movable ncm; +}; + +non_copy_movable return_reference::ncm; + +//A wrapper that locks a mutex while the +//factory creates a new value. +//It must generically move the return value +//if possible both in C++03 and C++11 +template +typename Factory::return_type lock_wrapper(Factory f) +{ + typedef typename Factory::return_type return_type; + //LOCK(); + return_type r = f(); + //UNLOCK(); + + //In C++03: boost::move() if R is not a reference and + //has move emulation enabled. In C++11: just return r. + return BOOST_MOVE_RET(return_type, r); +} + +int main() +{ + movable m = lock_wrapper(factory_functor ()); + copy_movable cm = lock_wrapper(factory_functor()); + copyable c = lock_wrapper(factory_functor ()); + non_copy_movable &mr = lock_wrapper(return_reference ()); + //<- + (void)m; (void)cm; (void)c; (void)mr; + //-> + return 0; +} +//] + +#include diff --git a/include/boost/move/core.hpp b/include/boost/move/core.hpp index 9586eca..3e2a7ba 100644 --- a/include/boost/move/core.hpp +++ b/include/boost/move/core.hpp @@ -168,6 +168,46 @@ const ::boost::rv< TYPE >& \ // + namespace boost { + namespace move_detail { + + template + inline typename ::boost::move_detail::enable_if_c + < ::boost::move_detail::is_lvalue_reference::value || + !::boost::has_move_emulation_enabled::value + , T&>::type + move_return(T& x) BOOST_NOEXCEPT + { + return x; + } + + template + inline typename ::boost::move_detail::enable_if_c + < !::boost::move_detail::is_lvalue_reference::value && + ::boost::has_move_emulation_enabled::value + , ::boost::rv&>::type + move_return(T& x) BOOST_NOEXCEPT + { + return *static_cast< ::boost::rv* >(::boost::move_detail::addressof(x)); + } + + template + inline typename ::boost::move_detail::enable_if_c + < !::boost::move_detail::is_lvalue_reference::value && + ::boost::has_move_emulation_enabled::value + , ::boost::rv&>::type + move_return(::boost::rv& x) BOOST_NOEXCEPT + { + return x; + } + + } //namespace move_detail { + } //namespace boost { + + #define BOOST_MOVE_RET(RET_TYPE, REF)\ + boost::move_detail::move_return< RET_TYPE >(REF) + // + ////////////////////////////////////////////////////////////////////////////// // // BOOST_MOVABLE_BUT_NOT_COPYABLE @@ -220,9 +260,12 @@ #elif defined(_MSC_VER) && (_MSC_VER == 1600) //Standard rvalue binding rules but with some bugs #define BOOST_MOVE_MSVC_10_MEMBER_RVALUE_REF_BUG + #define BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG //Use standard library for MSVC to avoid namespace issues as //some move calls in the STL are not fully qualified. //#define BOOST_MOVE_USE_STANDARD_LIBRARY_MOVE + #elif defined(_MSC_VER) && (_MSC_VER == 1700) + #define BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG #endif #endif @@ -298,7 +341,6 @@ // #if !defined(BOOST_MOVE_DOXYGEN_INVOKED) - /// @cond #define BOOST_RV_REF_2_TEMPL_ARGS(TYPE, ARG1, ARG2)\ TYPE && \ @@ -328,10 +370,69 @@ const TYPE & \ // - /// @endcond #endif //#if !defined(BOOST_MOVE_DOXYGEN_INVOKED) + #if !defined(BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG) || defined(BOOST_MOVE_DOXYGEN_INVOKED) + + //!This macro is used to achieve portable move return semantics. + //!The Standard allows implicit move returns when the object to be returned + //!is designated by an lvalue and: + //! - The criteria for elision of a copy operation are met OR + //! - The criteria would be met save for the fact that the source object is a function parameter + //! + //!For C++11 conforming compilers this macros only yields to REF: + //! return BOOST_MOVE_RET(RET_TYPE, REF); -> return REF; + //! + //!For compilers without rvalue references + //!this macro does an explicit move if the move emulation is activated + //!and the return type (RET_TYPE) is not a reference. + //! + //!For non-conforming compilers with rvalue references like Visual 2010 & 2012, + //!an explicit move is performed if RET_TYPE is not a reference. + //! + //! Caution: When using this macro in a non-conforming or C++03 + //!compilers, a move will be performed even if the C++11 standard does not allow it + //!(e.g. returning a static variable). The user is responsible for using this macro + //!only used to return local objects that met C++11 criteria. + #define BOOST_MOVE_RET(RET_TYPE, REF)\ + (REF) + // + + #else //!defined(BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG) || defined(BOOST_MOVE_DOXYGEN_INVOKED) + + #include + + namespace boost { + namespace move_detail { + + template + inline typename ::boost::move_detail::enable_if_c + < ::boost::move_detail::is_lvalue_reference::value + , T&>::type + move_return(T& x) BOOST_NOEXCEPT + { + return x; + } + + template + inline typename ::boost::move_detail::enable_if_c + < !::boost::move_detail::is_lvalue_reference::value + , Ret && >::type + move_return(T&& t) BOOST_NOEXCEPT + { + return static_cast< Ret&& >(t); + } + + } //namespace move_detail { + } //namespace boost { + + #define BOOST_MOVE_RET(RET_TYPE, REF)\ + boost::move_detail::move_return< RET_TYPE >(REF) + // + + #endif //!defined(BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG) || defined(BOOST_MOVE_DOXYGEN_INVOKED) + #endif //BOOST_NO_CXX11_RVALUE_REFERENCES #include diff --git a/proj/vc7ide/Move.sln b/proj/vc7ide/Move.sln index 6c995e7..a27c40a 100644 --- a/proj/vc7ide/Move.sln +++ b/proj/vc7ide/Move.sln @@ -59,6 +59,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "move_test", "move_test.vcpr ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "doc_move_return", "doc_move_return.vcproj", "{7C1462C8-D532-4B8E-F2F6-E3A2A796D912}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -127,6 +131,10 @@ Global {CD57C283-1862-42FE-BF87-B96D3A2A7912}.Debug.Build.0 = Debug|Win32 {CD57C283-1862-42FE-BF87-B96D3A2A7912}.Release.ActiveCfg = Release|Win32 {CD57C283-1862-42FE-BF87-B96D3A2A7912}.Release.Build.0 = Release|Win32 + {7C1462C8-D532-4B8E-F2F6-E3A2A796D912}.Debug.ActiveCfg = Debug|Win32 + {7C1462C8-D532-4B8E-F2F6-E3A2A796D912}.Debug.Build.0 = Debug|Win32 + {7C1462C8-D532-4B8E-F2F6-E3A2A796D912}.Release.ActiveCfg = Release|Win32 + {7C1462C8-D532-4B8E-F2F6-E3A2A796D912}.Release.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionItems) = postSolution ..\..\..\..\boost\move\algorithm.hpp = ..\..\..\..\boost\move\algorithm.hpp diff --git a/proj/vc7ide/doc_move_return.vcproj b/proj/vc7ide/doc_move_return.vcproj new file mode 100644 index 0000000..eeedd94 --- /dev/null +++ b/proj/vc7ide/doc_move_return.vcproj @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/move.cpp b/test/move.cpp index 4d6eae7..5ae0854 100644 --- a/test/move.cpp +++ b/test/move.cpp @@ -11,6 +11,7 @@ #include #include #include "../example/movable.hpp" +#include "../example/copymovable.hpp" #include movable function(movable m) @@ -57,11 +58,41 @@ void function_ref(const movable &) void function_ref(BOOST_RV_REF(movable)) {} -struct copyable -{}; - movable create_movable() { return movable(); } + +template +struct factory +{ + Type operator()() const + { + Type t; + return BOOST_MOVE_RET(Type, t); + } +}; + +template +struct factory +{ + static Type t; + Type &operator()() const + { + return BOOST_MOVE_RET(Type&, t); + } +}; + +template +Type factory::t; + +template +R factory_wrapper(F f) +{ + // lock(); + R r = f(); + // unlock(); + return BOOST_MOVE_RET(R, r); +} + int main() { #if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) @@ -78,35 +109,72 @@ int main() movable m2(boost::move(m)); movable m3(function(movable(boost::move(m2)))); movable m4(function(boost::move(m3))); + (void)m;(void)m2;(void)m3;(void)m4; } { movable m; movable m2(boost::move(m)); movable m3(functionr(movable(boost::move(m2)))); movable m4(functionr(boost::move(m3))); + (void)m;(void)m2;(void)m3;(void)m4; } { movable m; movable m2(boost::move(m)); movable m3(function2(movable(boost::move(m2)))); movable m4(function2(boost::move(m3))); + (void)m;(void)m2;(void)m3;(void)m4; } { movable m; movable m2(boost::move(m)); movable m3(function2r(movable(boost::move(m2)))); movable m4(function2r(boost::move(m3))); + (void)m;(void)m2;(void)m3;(void)m4; } { movable m; movable m2(boost::move(m)); movable m3(move_return_function()); + (void)m;(void)m2;(void)m3; } { movable m; movable m2(boost::move(m)); movable m3(move_return_function2()); + (void)m;(void)m2;(void)m3; } + { + //movable + movable m (factory_wrapper(factory())); + m = factory_wrapper(factory()); + movable&mr(factory_wrapper(factory())); + movable&mr2 = factory_wrapper(factory()); + (void)mr; + (void)mr2; + (void)m; + } + { + //copyable + copyable c (factory_wrapper(factory())); + c = factory_wrapper(factory()); + copyable&cr(factory_wrapper(factory())); + copyable&cr2 = factory_wrapper(factory()); + (void)cr; + (void)cr2; + (void)c; + } + + { + //copy_movable + copy_movable c (factory_wrapper(factory())); + c = factory_wrapper(factory()); + copy_movable&cr(factory_wrapper(factory())); + copy_movable&cr2 = factory_wrapper(factory()); + (void)cr; + (void)cr2; + (void)c; + } return 0; }