Boost logo

Boost :

Subject: Re: [boost] [move][container] Review Request (new versions of Boost.Move and Boost.Container in sandbox and vault)
From: Thomas Klimpel (Thomas.Klimpel_at_[hidden])
Date: 2009-09-06 18:19:36


Thomas Klimpel wrote:
> Even if the advice
>
> "Any function that takes an r-value argument should try to free
> the resources owned by its r-value argument before returning,
> either by explicitly freeing the resources or by forwarding the
> r-value to another function (thereby forwarding the responsibility
> to try to free the resources owned by its r-value argument)."
>
> would be present in some important place, the semantics of
> 'clear(); swap();' for move assignment would still be questionable,
> because the memory owned by the vector is not freed.

I tried to read a bit more about r-value references, and came to the conclusion that the above advice is not a good idea. Passing arguments by r-value reference is much closer related to passing arguments by l-value reference than to pass arguments by value. Especially, neither slicing nor destructor calls happen. The main difference seems to be that an r-value reference can also bind to a temporary, but is often a worse match than an l-value reference.

>
>
> So what's my own opinion on this topic now?
> Trying to free resources owned by r-value arguments before
> returning is probably a good idea, because it is often what the
> programmer expects, it improves decoupling of concerns and
> reduces unexpected surprised when forwarding of r-value arguments.

Even so it looked like a good idea, it is not. It wouldn't work anyway, because nobody would abide to such a rule, and you wouldn't be able to ensure that you really free the resources for all possible control paths, because RAII would no longer be able to help you. But what's more important is that it doesn't make sense for the semantic of pass by r-value reference, because pass by r-value reference is too different from pass by values that trying to emulate the behavior of pass by value would make sense.

> As a consequence, even so implementing the move-assignment operator
> as a pure swap looks temping at first sight, this is not the best definition.

This statement may still be true (only the "As a consequence" is not true). One reason is that std::auto_ptr is sometimes considered as an example for a class that has an assignment operator with move semantics. But the move-assignment operator of std::auto_ptr is not implemented as a pure swap, but frees the resources. As a consequence, most smart pointers will probably follow the precedence of std::auto_ptr, and free the resources in the move-assignment operator. So it might indeed be surprising if the resources would not be freed for a vector of smart pointers.

Howard Hinnant wrote:
> The difference might be important when the vector holds items whose destructors
> have side effects whose timing might be significant (e.g. vector<thread>).
>
> LWG 675 (http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#675) is currently tracking this issue.

So what's my own opinion on this topic now? I don't know any more. I now have to impression that r-value references opened more questions than the proposals answered explicitly. The swap(T&, T&&) functions are examples of this. (I think they are simply a bad idea, but they may be voted into the standard nevertheless). The swap functions also raise the question about the type of the implicit "*this" argument of a member function. Compatibility with prior behavior seems to dictate that its type must be "T&&". But should there be a way to have member functions where the implicit type is T&? (Similar to const member function where "*this" has the type "const T&"). The swap function also raises interesting questions about slicing behavior, because the base class of a derived class might be swapped away, possibly violating some class invariant.

So now I'm waiting for the next part of the article "http://cpp-next.com/archive/2009/08/want-speed-pass-by-value", because as Dave Abrahams wrote:
> Hold your horses, people! All (well, much) will be revealed when we cover rvalue refs in the next installment. :-)

While at it, I wrote some tests to compare boost::move and c++0x rvalue refs behavior. I attached the file "rvalue.cpp", which only compiles in C++0x mode, the file "moveemulation.cpp", which also compiles without C++0x mode, and reproduces the behavior of rvalue.cpp when compiled in C++0x mode. I also attached the output of rvalue.cpp in C++0x mode ("rvalue.log"), the output of moveemulation.cpp in C++0x mode ("move0x.log"), and the output of moveemulation.cpp without C++0x mode ("moveemulation.log").
As intended, "diff rvalue.log move0x.log" reports that rvalue.log and move0x.log are identical. On the other hand "diff -u move0x.log moveemulation.log" reports:
--- move0x.log 2009-09-06 23:55:49.015625000 +0200
+++ moveemulation.log 2009-09-06 23:55:39.671875000 +0200
@@ -1,7 +1,7 @@

 base constructors:
 ++d_: 1
-bmc
+bcc
 ++b_: 1

 base(), b, cb:
@@ -38,6 +38,6 @@

 slicing assignments:
 bca
-bma
+bca
 ++d_: 1
 bma

It turns out that the differences are exactly the slicing move constructors and move assignments. In C++0x mode, the slicing move constructors and move assignment operators are selected, but without C++0x mode the normal copy constructors and copy assignment operators are used. But I have to admit that I tested only with gcc 4.3.2, perhaps other compilers will produce different results. I was quite surprised how successful Boost.Move was able to emulate the C++0x rvalue references.

Regards,
Thomas








Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk