2017-01-13 98 views
0

我有一个C++框架,我提供给我的用户,他们应该使用我用自己的实现编写的模板包装作为模板类型。 包装器充当RAII类,它包含一个指向用户类实现的指针。 为了使用户的代码干净整洁(在我看来),我提供了一个转换操作符,它将包装器转换为它所包含的指针。这种方式(以及其他一些重载)用户可以使用我的包装,就好像它是一个指针(很像shared_ptr)。C++ std ::移动指针

我遇到了一个角落的情况,用户调用一个函数,该函数使用指向他的实现类的指针,在我的包装上使用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; 
} 

[这是ofcourse只是案件的一个例子,有在包装更多的方法,但我敢肯定,在这里不发挥作用在这种情况下]

用户,如我所料,如果在包装上调用std :: move,那么在调用foo之后,包装将是空的(或者至少像被移动一样进行修改) ,但实际上在foo之前调用的唯一方法是演员。

有没有一种方法,使两个调用使用和不std::move打电话时foo即区分之间的调用foo

编辑 得益于鸣叫鸭的评论,我发现一种方式,my_wrapper知道哪些电话是必需的,但我真的不知道这是一起去的最佳方法,并赞赏这样的评论,以及:

而不是以前的类型转换操作符的使用以下两种:

operator T*() & { 
    return m_ptr; 
} 

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

现在operator T*() &&用的std ::移动和operator T*() &打电话时被称为没有它调用时被调用。

+0

您是指在呼叫站点还是被调用者区分? –

+0

被调用者的意思是'my_impl'类型? – ZivS

+3

用户的期望是不合理的。如果你对某个东西调用了'std :: move',并将它传递给一个没有占用移动对象的函数,那么期望这个对象是空的或者更改是不合理的。例如:'std :: shared_ptr foo; ... std :: move(foo) - > bar();'这不应该改变'foo',因为'bar'不占有。如果'bar(std :: move(foo));'也是如此。 'std :: move'只是给被调用的函数*权限*来移动对象,如果没有指定它,它不会强制它移动它。 –

回答

3

用户,正如我预计,如果性病::此举被称为在包装,然后调用后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 
} 

可是......为什么呢?为什么要这样做?

,如果你做的包装不会留空:

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); 

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

即使你包装有一个移动构造函数和移动赋值操作符,这样上面的例子离开w空,仍然并不意味着访问一个右值对象的成员应该修改的对象。为什么operator T*转换是对左值还是右值做了逻辑区分?

(另外,你真的能肯定的是同时具有从包裹指针类型隐式转换是一个好主意?提示:这不是一个好主意,一般更愿意让你转换明确,特别。如果您正在处理指向动态分配对象的指针)

+1

一年后,阅读你的答案是有道理的,我可以说,第一次阅读这个答案对我来说感觉不对,但现在我知道这只是因为我没有完全理解移动语义。谢谢 :) – ZivS