C++ std::move 一个指针

Ziv*_*ivS 1 c++ templates operator-overloading move-semantics c++11

我有一个 C++ 框架,我提供给我的用户,他们应该使用我用自己的实现编写的模板化包装器作为模板化类型。包装器充当 RAII 类,它持有一个指向用户类实现的指针。为了使用户的代码干净整洁(在我看来),我提供了一个强制转换运算符,它将我的包装器转换为它所持有的指针。这样(连同其他一些重载)用户可以使用我的包装器,就好像它是一个指针(很像一个 shared_ptr)。

我遇到了一个极端情况,用户调用一个函数,该函数使用指向他的实现类的指针,在我的包装器上使用 std::move。下面是它的外观示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

[这当然只是一个例子,包装器中还有更多方法,但我很确定在这种情况下不会在这里起作用]

和我一样,用户期望如果在包装器上调用 std::move,那么在调用foo包装器之后将是空的(或至少修改为好像它被移动了),但实际上调用的唯一方法beforefoo是强制转换运算符。

有没有办法使调用foo区分两个调用fooie 时调用和不调用std::move

编辑 感谢 Mooing Duck 的评论,我找到了一种my_wrapper知道需要哪个调用的方法,但我真的不确定这是最好的方法,并且也会感谢对此的评论:

使用以下两个代替之前的强制转换运算符:

operator T*() & {
    return m_ptr;
}

operator T*() &&{
    //Do something
    return m_ptr;
}
Run Code Online (Sandbox Code Playgroud)

nowoperator T*() &&在使用 std::moveoperator T*() &调用时调用,在不使用 std::move调用时调用。

Jon*_*ely 6

用户和我一样,希望如果在包装器上调用 std::move,那么在调用 foo 之后,包装器将为空(或至少像被移动一样修改)

你的期望是错误的。只有在发生移动时才会修改它,即如果某种资源的所有权被转移。但是调用foo并没有做任何类似的事情,因为它只能访问包装器中保存的指针。调用std::move除了将其参数强制转换为右值外不会做任何事情,而右值不会改变它。某些通过引用接受右值的函数可能会修改它,因此std::move启用它,但它本身不会这样做。如果您不将右值传递给这样的函数,则不会发生任何修改。

如果你真的想让它为空,你可以添加一个重载来做到这一点:

template<typename T>
void foo(my_wrapper<T>&& w) {
    foo(static_cast<my_interface*>(w));
    w = my_wrapper<T>{};  // leave it empty
}
Run Code Online (Sandbox Code Playgroud)

但为什么?为什么要这样做?

如果您这样做,包装器不会留空:

my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2 = std::move(w);
Run Code Online (Sandbox Code Playgroud)

并且不是空的:

my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2;
w2 = std::move(w);
Run Code Online (Sandbox Code Playgroud)

如果复制右值包装器不会将其留空,为什么简单地访问其成员将其留空?这是没有意义的。

即使您的包装器具有移动构造函数和移动赋值运算符,因此上面的示例确实保留为w空,但这仍然并不意味着访问右值对象的成员应该修改该对象。为什么operator T*转换为左值或右值会产生任何逻辑差异?

(另外,你真的相信有既隐式转换从包裹指针类型是一个好主意?提示:这不是一个好主意,一般更愿意让你转换明确。尤其是如果你正在处理的指针动态分配的对象。)

  • 一年后,阅读你的答案是有道理的,我可以说第一次阅读这个答案对我来说是错误的,但我现在知道这只是因为我没有完全理解移动语义。谢谢 :) (4认同)