简体   繁体   中英

C++ std::move a pointer

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 before foo is the cast operator.

Is there a way to make the call to foo distinguishable between the two calls to foo ie when calling with and without std::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 and operator T*() & is called when calling without it.

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, ie 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.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM