C ++ std ::移动指针

时间:2017-01-13 00:34:00

标签: c++ c++11 templates operator-overloading move-semantics

我有一个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;
}

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

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

有没有办法让foo之间的呼叫在foo的两次呼叫之间有区别,即在使用和不使用std::move时进行呼叫?

修改 感谢Mooing Duck的评论,我找到了my_wrapper知道需要哪个电话的方式,但我真的不确定这是最好的方法,也会对此表示赞赏:

而不是之前的强制转换操作符使用以下两个:

operator T*() & {
    return m_ptr;
}

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

现在使用std :: move调用时调用operator T*() &&,并且在没有调用时调用operator T*() &

1 个答案:

答案 0 :(得分:3)

  

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

你的期望是错误的。只有在移动发生时,即如果转移了某种资源的所有权,它才会被修改。但是调用foo并没有做那样的事情,因为它只是访问包装器中保存的指针。调用std::move除了将其参数转换为rvalue之外不会做任何事情,而rvalue不会改变它。某些通过引用接受rvalue的函数可能会修改它,因此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);

如果复制rvalue包装器不会将其留空,为什么只需访问其成员就将其留空?这毫无意义。

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

(另外,你真的确定从包装指针类型隐式转换到是一个好主意吗?提示:这不是一个好主意。通常更喜欢让你的转换显式,特别是,如果你正在处理动态分配对象的指针。)