C++ std::move a pointer
The user, as would I, expect that if std::move was called on the wrapper, then after the call to foo the wrapper will be empty (or at least modified as if it was moved)
Your expectation is wrong. It will only be modified if a move happens, i.e. if ownership of some kind of resource is transferred. But calling foo
doesn't do anything like that, because it just gets access to the pointer held inside the wrapper. Calling std::move
doesn't do anything except cast its argument to an rvalue, which doesn't alter it. Some function which accepts an rvalue by reference might modify it, so std::move
enables that, but it doesn't do that itself. If you don't pass the rvalue to such a function then no modification takes place.
If you really want to make it empty you can add an overload to do that:
template<typename T>
void foo(my_wrapper<T>&& w) {
foo(static_cast<my_interface*>(w));
w = my_wrapper<T>{}; // leave it empty
}
But ... why? Why should it do that?
The wrapper isn't left empty if you do:
my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2 = std::move(w);
And isn't left empty by:
my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2;
w2 = std::move(w);
If copying an rvalue wrapper doesn't leave it empty, why should simply accessing its member leave it empty? That makes no sense.
Even if your wrapper has a move constructor and move assignment operator so that the examples above do leave w
empty, that still doesn't mean that accessing the member of an rvalue object should modify the object. Why does it make any logical difference whether the operator T*
conversion is done to an lvalue or an rvalue?
(Also, are you really sure that having implicit conversions both to and from the wrapped pointer type is a good idea? Hint: it's not a good idea. In general prefer to make your conversions explicit, especially if you're dealing with pointers to dynamically-allocated objects.)
Comments
-
ZivS almost 2 years
I have a C++ framework which I provide to my users, who should use a templated wrapper I wrote with their own implementation as the templated type. The wrapper acts as an RAII class and it holds a pointer to an implementation of the user's class. To make the user's code clean and neat (in my opinion) I provide a cast operator which converts my wrapper to the pointer it holds. This way (along with some other overloads) the user can use my wrapper as if it is a pointer (much like a shared_ptr).
I came across a corner case where a user calls a function, which takes a pointer to his implementation class, using std::move on my wrapper. Here's an example of what it looks like:
#include <iostream> using namespace std; struct my_interface { virtual int bar() = 0; }; template <typename T> struct my_base : public my_interface { int bar() { return 4; } }; struct my_impl : public my_base<int> {}; template <typename T> struct my_wrapper { my_wrapper(T* t) { m_ptr = t; } operator T*() { return m_ptr; } private: T* m_ptr; }; void foo(my_interface* a) { std::cout << a->bar() << std::endl; } int main() { my_impl* impl = new my_impl(); my_wrapper<my_impl> wrapper(impl); foo(std::move(wrapper)); //foo(wrapper); return 0; }
[This is ofcourse just an example of the case, and there are more methods in the wrapper, but I'm pretty sure that don't play a role here in this case]
The user, as would I, expect that if std::move was called on the wrapper, then after the call to
foo
the wrapper will be empty (or at least modified as if it was moved), but in reality the only method being invoked beforefoo
is the cast operator.Is there a way to make the call to
foo
distinguishable between the two calls tofoo
i.e when calling with and withoutstd::move
?EDIT Thanks to the Mooing Duck's comment I found a way that
my_wrapper
knows which call is required, but I'm really not sure this is the best method to go with and will appreciate comments on this as well:Instead of the previous cast operator use the following two:
operator T*() & { return m_ptr; } operator T*() &&{ //Do something return m_ptr; }
now
operator T*() &&
is called when calling with std::move andoperator T*() &
is called when calling without it.-
Kerrek SB over 7 yearsDo you mean distinguishable at the call site or by the callee?
-
ZivS over 7 yearsBy the callee you mean the
my_impl
type? -
David Schwartz over 7 yearsThe user's expectation is unreasonable. If you call
std::move
on something and pass that to a function that does not take possession of the moved object, expecting the object to be empty or changed is unreasonable. For example:std::shared_ptr<X> foo; ... std::move(foo)->bar();
This should not changefoo
becausebar
does not take possession. Same ifbar(std::move(foo));
. Thestd::move
just gives the called function permission to move the object, it doesn't force it to move it if it isn't specified to. -
ZivS over 7 years@DavidSchwartz, so do you think that
my_wrapper
's API is "well defined" in this case? -
David Schwartz over 7 yearsPersonally, I don't like it. What does it do that
unique_ptr
doesn't do? Semantically, it's supposed to behave like a pointer, right? Why would you expectstd::move(pointer)
to do anything special? -
ZivS over 7 yearsmainly it is itanium abi compliant
-
ZivS over 7 years"Why would you expect std::move(pointer) to do anything special?" - actually I was so caught up with the fact the the wrapper was being moved that I forgot this is how it should be treated, as a pointer
-
ZivS over 7 yearsstill I wonder, if there is a way to have my_wrapper or my_interface detect that its pointer is sent to std::move?
-
Mooing Duck over 7 years@ZivS: Sure. If
operator T*() &&
is called, then someone passed it as an rvalue to a function expecting aT*
. -
Mooing Duck over 7 yearsunrelated, affirm that
my_wrapper
obeys the rule of five, and that thatmy_wrapper(T*)
constructor is explicit. -
ZivS over 7 years@MooingDuck, it does not seem to compile with your suggestion
-
Kerrek SB over 7 years@ZivS: No, I mean
foo
. Shouldfoo
know how it was called? -
ZivS over 7 years@MooingDuck, please see my edit to the post
-
ZivS over 7 years@KerrekSB, no. foo is not even my function (as the API creator)
-
Kerrek SB over 7 yearsThen what exactly do you want to achieve? Me, I would simply decide to not provide the implicit conversion from either an lvalue or an rvalue of
wrapper
. (But I'm not sure which one.)
-
-
ZivS over 6 yearsa year later, reading your answer makes sense and I can say that reading this answer the first time felt wrong to me, but I know now that it was only because I did not fully understood move semantics. Thanks :)