Proper handling of temporary objects


Purpose

Making a wrapper that keeps reference to some constructor arguments.
That’s OK as long as the wrapper lifetime is longer or equal to its construction arguments.

If the wrapper is created from temporary objects, it should better be used in the same line _or_ it will make reference to possibly destroyed objects.

Example

#include <iostream> #include <string> using namespace std::literals; struct Object { std::string name; Object(std::string n) : name{n} { std::cout << "Created " << name << std::endl; } ~Object() { std::cout << "Destroyed " << name << std::endl; name = "**"s + name + "**"s;} Object(const Object& rhs) : Object{ "copy of "s + rhs.name } { } Object& operator=(const Object& rhs) { name = "copy of"s + rhs.name; std::cout << "Copied " << rhs.name << std::endl; return *this; } }; template <typename T> struct Call { const T& obj; Call(const T& objIn) : obj{objIn} { } void handle() { std::cout << "handle call to " << obj.name << std::endl; } }; template <typename T> auto makeCall(const T& obj) { return Call{ obj }; } int main() { Object x { "constructed" }; std::cout << "*** auto cx = Call<Object>{x}" << std::endl; auto cx = Call<Object>{x}; cx.handle(); std::cout << "*** auto cx2 = makeCall(x)" << std::endl; auto cx2 = makeCall(x); cx2.handle(); std::cout << "*** auto ct2 = makeCall( Object{\"temporary2\"} )" << std::endl; auto ct2 = makeCall( Object{"temporary2"} ); ct2.handle(); }
Code language: PHP (php)

Available on Coliru

Output

Created constructed
*** auto cx = Call<Object>{x}
handle call to constructed
*** auto cx2 = makeCall(x)
handle call to constructed
*** auto ct2 = makeCall( Object{"temporary2"} )
Created temporary2
Destroyed temporary2
handle call to **temporary2**
Destroyed constructed

Solution

Make an extra layer of wrapper that would handle the cases where the wrapper is constructed from temporary objects.

First attempt

Replace the end (from makeCall() definition onwards) with

template <typename T> struct Wrapped { T obj; Call<T> innerCall; Wrapped(T&& tmp) : obj{tmp}, innerCall{obj} { } void handle() { std::cout << "wrapped call: "; innerCall.handle(); } }; template <typename T> auto makeCall(T&& tmp) { return Wrapped<T>{ std::forward<T>(tmp) }; } template <typename T> auto makeCall(const T& obj) { return Call<T>{ obj }; } int main() { Object x { "constructed" }; std::cout << "*** auto cx = Call<Object>{x}" << std::endl; auto cx = Call<Object>{x}; cx.handle(); std::cout << "*** auto ct = Wrapped<Object>{ Object{\"temporary\"} }" << std::endl; auto ct = Wrapped<Object>{ Object{"temporary"} }; ct.handle(); std::cout << "*** auto cx2 = makeCall(x)" << std::endl; auto cx2 = makeCall(x); cx2.handle(); std::cout << "*** auto ct2 = makeCall( Object{\"temporary2\"} )" << std::endl; auto ct2 = makeCall( Object{"temporary2"} ); ct2.handle(); }
Code language: PHP (php)

See on Coliru

Output

Created constructed
*** auto cx = Call<Object>{x}
handle call to constructed
*** auto ct = Wrapped<Object>{ Object{"temporary"} }
Created temporary
Created copy of temporary
Destroyed temporary
wrapped call: handle call to copy of temporary
*** auto cx2 = makeCall(x)
wrapped call: handle call to constructed
*** auto ct2 = makeCall( Object{"temporary2"} )
Created temporary2
Created copy of temporary2
Destroyed temporary2
wrapped call: handle call to copy of temporary2
Destroyed copy of temporary2
Destroyed copy of temporary
Destroyed constructed

More convoluted way

Replace the two makeCall() overloads with

template <typename T, bool needCopy = false> struct CallSelected { using Type = Call<T>; }; template <typename T> struct CallSelected<T, true> { using Type = Wrapped<T>; }; template <typename T> struct CallSelector { using Type = typename CallSelected< std::decay_t<T>, std::is_rvalue_reference_v<T> >::Type; }; template <typename T> auto makeCall(T&& obj) { using Type = typename CallSelector<decltype(obj)>::Type; return Type{ std::forward<T>(obj) }; }
Code language: PHP (php)

See also on Coliru, Produces the same output

Share

Les commentaires sont fermés.