1

Suppose there is a function that returns any local object, which implements move semantics, e.g. any STL container such as std::vector, std::string, etc. E.g.:

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

By default, tmp would be considered as an rvalue and tmp will be moved (or return value optimization will take place).

The question is how to manually override and avoid C++11's default behavior and execute copy constructor instead of move here? One solution may be to implement a wrapper class for std::vector with move semantics disabled, but this doesn't seem like a nice option.

One of the reasons for such desire may be that the caller is in another assembly and if the runtime libraries are statically linked, there would be two heaps. Moving will result in memory assertions due to allocation/deleting memory across DLLs boundaries.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Oleg Shirokikh
  • 3,447
  • 4
  • 33
  • 61
  • https://stackoverflow.com/questions/17473753/c11-return-value-optimization-or-move – Cory Kramer May 19 '15 at 19:52
  • 1
    Trying to use standard library classes through an interface across dll boundaries is dangerous, period, regardless of if you move or copy to do so. – Cory Kramer May 19 '15 at 19:53
  • 3
    If you copy in `return_vector` then the copy will still be allocated from the same heap on which `tmp`'s storage was originally allocated, so you don't save anything there. (C++ across DLLs is asking for pain) – Billy ONeal May 19 '15 at 19:53
  • 1
    also see: http://stackoverflow.com/questions/22797418/how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dll – NathanOliver May 19 '15 at 19:55

3 Answers3

2

The question is how to avoid C++11's default behavior and execute copy constructor instead of move here?

That isn't the default behaviour. The default behaviour would be to elide the copy in this case. The move would only take place in the unlikely case that the implementation does not implement NRVO.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
2

The short answer is, you cannot make return f; not move. When you return in C++, elision is default, and if not that it is moved, and if not that it is copied. If you use a non-trivial statement -- even true?v:v, or static_cast<whatever const&>(v) -- it will prevent auto-move and force a copy. But that won't help you.

Avoiding the move won't help you. The return object is still created within the function, and it is disposed of by the calling code.

Now, not all is lost. You can avoid this by the use of header files (which exist within the client code) doing the allocation, and a DLL-safe interface (to the implementation).

Here I design a sink type, which sucks in data of type T, in batches. It then calls some function pointer with a pvoid and is done.

template<class T>
struct sink {
  void* s;
  void(*)(void*, T const*, T const*) f;
  void operator()(T const& t)const{ f(s, &t, (&t)+1); }
  void operator()(std::initializer_list<T> il) {
    f(s, il.begin(), il.end());
  }
};
template<class T, class A>>
sink<T> vector_sink( std::vector<T, A>& out ) {
  return {&out, +[](void* s, T const* b, T const* e){
    auto* pout = static_cast<std::vector<T,A>*>(s);
    pout->insert( pout->end(), b, e );
  }};
}

now, export from the DLL:

void make_data(sink<int> s) {
  s({1,2,3,4,5});
}

in a header file, expose:

void make_data(sink<int> s);
std::vector<int> return_vector() {
  std::vector<int> r;
  make_data( vector_sink(r) );
}

and now the vector lives completely in the client code of the DLL. Only a standard layout class (consisting of 2 pointers) crosses the DLL barrier.

A fancier sink could distinguish between rvalues and lvalues by just adding a new function (for move-data-in). However, that seems unwise if this is intended to bridge DLL boundaries.

This handles "returning" a vector. To take a vector in (without appending), I'd advise writing array_view<int> which wraps two int*s: similarly, standard layout that is pretty safe to cross DLL boundaries with.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

Performing a static_cast to reference will do (example use movable class C, which can be vector)

cast to lvalue reference disables both move and NRVO

C f() {
    C c;
    return static_cast<C&>(c);
}

cast to rvalue reference disables just NRVO

C f() {
    C c;
    return static_cast<C&&>(c);
}
Peter K
  • 1,787
  • 13
  • 15