Best Practice: Bedingte Kopie
-
Hallo
Was bei mir immer mal wieder auftritt, ist, dass ich bedingt eine teure Kopie machen muss innerhalb einer Funktion. Das soll nicht nötig sein, wenn die Funktion mit einer Rvalue-Referenz aufgerufen wird. Im folgenden Beispiel wird die Funktion
g
das Argument verändern:template< typename T > int g( T& x ); template< typename T > int f( T&& x ) { if( x.size() == 42 ) // irgendeine Bedingung, die das Kopieren hinfällig macht return 0; auto Copy = std::forward< T >( x ); return g( Copy ); }
Meine Frage: Würdet ihr das gleich machen? Oder gibt es hierfür eine einfachere Lösung, die ich gerade übersehe?
LG
-
Einfacher vermutlich nicht.
Aber es gibt sicher ne kompliziertere Möglichkeit, die den Move vermeidet wenn das Argument von f schon ne Rvalue-Referenz ist
-
hustbaer schrieb:
Aber es gibt sicher ne kompliziertere Möglichkeit, die den Move vermeidet wenn das Argument von f schon ne Rvalue-Referenz ist
Blöd ist, dass die Anzahl der Objekte, die beim verlassen des Scopes zerstört werden müssten, unterschiedlich wäre, das geht nicht so ohne Weiteres.
Möglich wäre etwas in der Art:template< typename T > int g( T& x ); template< typename T > int f( T&& x ) { if( x.size() == 42 ) // irgendeine Bedingung, die das Kopieren hinfällig macht return 0; return []( auto&& copy ) { // mach was mit copy return g( copy ); }( std::is_lvalue_reference< T >{} ? forward< std::remove_reference_t< T > >( T{ x } ) : forward< T >( x ) ); }
-
Hätte jetzt eher an sowas gedacht...
#include <type_traits> namespace detail { template <class T> class forward_ref_t { public: template <class U> forward_ref_t(U&& u) : m_ptr(&static_cast<U&>(u)) { } T& ref() { return *m_ptr; } private: T* m_ptr; }; template <class T> class forward_copy_t { public: template <class U> forward_copy_t(U&& u) : m_copy(std::forward<U>(u)) { } T& ref() { return m_copy; } private: T m_copy; }; template <class T> struct can_forward_as_ref { static bool const value = std::is_rvalue_reference<T>::value && !std::is_const<std::remove_reference<T>::type>::value; }; template <class T> struct base_type : std::remove_cv<typename std::remove_reference<T>::type> { }; template <class T, bool as_ref> struct forward_buffer_type_impl; template <class T> struct forward_buffer_type_impl<T, true> { typedef typename forward_ref_t<typename base_type<T>::type> type; }; template <class T> struct forward_buffer_type_impl<T, false> { typedef typename forward_copy_t<typename base_type<T>::type> type; }; template <class T> struct forward_buffer_type : forward_buffer_type_impl<T, can_forward_as_ref<T>::value> { }; } // namespace detail template <class T> typename detail::forward_buffer_type<T&&>::type forward_or_copy(typename std::remove_reference<T>::type&& t) { return std::forward<T>(t); } template <class T> typename detail::forward_buffer_type<T&&>::type forward_or_copy(typename std::remove_reference<T>::type& t) { return std::forward<T>(t); } template< typename T > int g(T& x) { x.mutate(); return x.size(); } template< typename T > int f(T&& x) { if (x.size() == 42) // irgendeine Bedingung, die das Kopieren hinfällig macht return 0; return g(forward_or_copy<T>(x).ref()); } //////////////////////////////////////////////////////////////////////// #include <iostream> class Movable { public: explicit Movable(size_t size) : m_size(size) { std::cout << "construct " << m_size << "\n"; } Movable(Movable const& other) : m_size(other.m_size) { std::cout << "copy-construct " << m_size << "\n"; } Movable(Movable&& victim) : m_size(victim.m_size) { victim.m_size = 0; std::cout << "move-construct " << m_size << "\n"; } ~Movable() { if (m_size) std::cout << "destruct " << m_size << "\n"; else std::cout << "destruct zombie\n"; } Movable& operator =(Movable&& victim) { m_size = victim.m_size; victim.m_size = 0; std::cout << "move-assign " << m_size << "\n"; } size_t size() const { return m_size; } void mutate() { m_size++; } private: size_t m_size; }; int main() { std::cout << "calling with lvalue...\n"; { Movable m(10); auto const result = f(m); std::cout << "result: " << result << "\n"; } std::cout << "calling with rvalue...\n"; { auto const result = f(Movable(20)); std::cout << "result: " << result << "\n"; } return 0; }
Geht aber sicher noch schöner (hab' mit C++11 Features keine Übung). Und zahlt sich natürlich nur aus wenns an mehr als nur 1-2 Stellen gebraucht wird.
ps: Fehlt bei dir ein
_v
(beiis_lvalue_reference< T >{}
)?
-
hustbaer schrieb:
ps: Fehlt bei dir ein
_v
(beiis_lvalue_reference< T >{}
)?std::integral_constant<T,..> hat einen Konvertierungsoperator nach T. Aus diesem Grunde sind die *_v Aliase nicht so nützlich wie *_t
Zudem gibt es sie erst ab C++17.Es geht auch noch ein bisschen kürzer:
#include <type_traits> #include <utility> template <class T> std::conditional_t<std::is_reference<T>{} || std::is_const<T>{}, std::remove_cv_t<std::remove_reference_t<T>>, T&&> forward_or_copy(T& t) { return std::forward<T>(t); } template <typename T> int g(T& x) { x.mutate(); return x.size(); } template <typename T> int f(T&& x) { if (x.size() == 42) // irgendeine Bedingung, die das Kopieren hinfällig macht return 0; auto&& xx = forward_or_copy<T>(x); return g(xx); } //////////////////////////////////////////////////////////////////////// #include <iostream> class Movable { public: explicit Movable(size_t size) : m_size(size) { std::cout << "construct " << m_size << "\n"; } Movable(Movable const& other) : m_size(other.m_size) { std::cout << "copy-construct " << m_size << "\n"; } Movable(Movable&& victim) : m_size(victim.m_size) { victim.m_size = 0; std::cout << "move-construct " << m_size << "\n"; } ~Movable() { if (m_size) std::cout << "destruct " << m_size << "\n"; else std::cout << "destruct zombie\n"; } Movable& operator=(Movable&& victim) { m_size = victim.m_size; victim.m_size = 0; std::cout << "move-assign " << m_size << "\n"; return *this; } size_t size() const { return m_size; } void mutate() { m_size++; } private: size_t m_size; }; int main() { std::cout << "calling with lvalue...\n"; { Movable m(10); auto const result = f(m); std::cout << "result: " << result << "\n"; } std::cout << "calling with rvalue...\n"; { auto const result = f(Movable(20)); std::cout << "result: " << result << "\n"; } }
-
Ein bisschen, ja Und ist auch viel schöner.
D.h. man kann die Lebenszeit eines Temporaries verlängern indem man ne Rvalue-Referenz damit initialisiert - so wie es auch mit const Lvalue-Referenzen geht? Würde ja auch irgendwie Sinn machen...
Potentiell verwirrend bei der Lösung ist natürlich dass man es jetzt nicht mehr ohne die Hilfsvariable schreiben kann. Ist aber IMO trotzdem viel schöner.
-
hustbaer schrieb:
Potentiell verwirrend bei der Lösung ist natürlich dass man es jetzt nicht mehr ohne die Hilfsvariable schreiben kann.
Man könnte so etwas wie
template <typename T> T& as_lvalue(T&& v) { return v; } template <typename T> int f(T&& x) { if (x.size() == 42) // irgendeine Bedingung, die das Kopieren hinfällig macht return 0; return g(as_lvalue(forward_or_copy<T>(x))); }
machen.