C++ std::move 一个指针

C++ std::move a pointer

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

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

#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;
}

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

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

有没有办法使对 foo 的调用在对 foo 的两次调用之间进行区分,即在调用和不调用 std::move 时?

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

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

operator T*() & {
    return m_ptr;
}

operator T*() &&{
    //Do something
    return m_ptr;
}

现在 operator T*() && 在使用 std::move 调用时调用,而 operator T*() & 在不使用它调用时调用。

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)

你的期望是错误的。它只会在发生移动时被修改,即如果某种资源的所有权被转移。但是调用 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
}

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

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

my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2 = std::move(w);

并且不会因以下原因留空:

my_wrapper<my_impl> w(new my_impl);
my_wrapper<my_impl> w2;
w2 = std::move(w);

如果复制右值包装器不会将其留空,那么为什么仅仅访问其成员就应该将其留空?这毫无意义。

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

(此外,您真的确定从包装指针类型到 的隐式转换是个好主意吗?提示:这不是个好主意。通常更喜欢为了使您的转换明确,尤其是 如果您正在处理指向 dynamically-allocated 对象的指针。)