为什么我们应该使用std :: move语义和唯一指针?

时间:2017-02-04 08:48:24

标签: c++ c++11 c++14 smart-pointers unique-ptr

概念性问题

假设我们有这样一个简单的例子:

 void foo(std::unique_ptr<int> ptr)
 {
    std::cout << *ptr.get() << std::endl;
 }
 int main()
 {
    std::unique_ptr<int> uobj = std::make_unique<int>(4);
    foo(uobj );  // line-(1) Problem ,but Alternative -> foo(std::move(uobj ))
    std::unique_ptr<int> uobjAlt = uobj; // line-(2) Problem ,but Alternative -> std::unique_ptr<int> uobjAlt = std::move(uobj);        
    return EXIT_SUCCESS;
}

我们知道简单地 std :: unique_ptr 与单个所有者拥有的资源概念绑定在多个所有者之间移动资源,而shared_ptr具有相反的方面。 如上所示,当您查看 line-(1)&amp; line-(2)你注意到违反了一些标准规则,因为std :: unique_ptr有(删除)没有定义复制构造函数和复制可赋值运算符,但为了避免编译错误,我们必须改为使用 std :: move 函数。

问题

为什么现代C ++编译器无法自动生成指令以在 line-(1) line-(2)中的唯一指针之间移动资源?因为我们知道有意设计的唯一指针。为什么我们应该明确地使用std :: move来指示机器移动资源的所有权?

std :: unique_ptr除了类模板之外什么都没有。我们知道,但是在第1行和第2行中遇到的情况有问题而编译器抱怨复制unique_pointers不允许(已删除的函数) .why我们有这些错误,为什么c ++标准和编译器供应商不能覆盖这个概念?

独特的Pointer故意设计用于在传递资源的同时传递其所有权,当我们将其作为函数/构造函数参数传递或分配给另一个唯一指针时,它在概念上应该将资源与所有权一起移动,但为什么我们应该使用std ::移动将编译器传达给实际移动,为什么我们不能自由地调用line-(1)和line-(2)呢? (虽然智能编译器为我们生成唯一指针之间的自动移动操作,除非有 const或非const引用传递)。

(对不起,有很长的描述和破碎的英语)谢谢。

3 个答案:

答案 0 :(得分:4)

unique_ptr超出范围时,

uobj自由内存非常有用。这是它的工作。所以,因为它有1个指针,它必须是空闲的,它必须是唯一的,因此它的名字是: unique_ptr

当你做这样的事情时:

std::unique_ptr<int> uobjAlt = uobj;

您正在发出 copy 操作,但是,您不应该复制指针,因为复制意味着对象uobjAltuobj都必须都是释放,这将直接导致分段错误和崩溃。因此,通过使用std::move,您将将所有权从一个对象移动到另一个对象。

如果您想拥有指向单个对象的多个指针,则应考虑使用std::shared_ptr

答案 1 :(得分:3)

这与编译器是否可以执行此操作无关。它当然可以这样工作,事实上,它在使用std::auto_ptr<>的C ++ 11之前确实有效。太可怕了。

std::auto_ptr<int> x = std::auto_ptr<int>(new int(5));
std::auto_ptr<int> y = x;
// Now, x is NULL

这里的问题是=符号通常表示“从x复制到y”,但在这种情况下,发生的是“从x移动到y,在过程中使x无效”。是的,如果你是一个精明的程序员,你会明白这里发生了什么,这不会让你感到惊讶,至少不是所有的时间。然而,在更常见的情况下,这将是非常令人惊讶的:

这是MyClass.h

class MyClass {
private:
    std::auto_ptr<Type> my_field;
    ...
};

这是MyClass.cpp

void MyClass::Method() {
    SomeFunction(my_field);
    OtherFunction(my_field);
}

这是Functions.h

// Which overload, hmm?
void SomeFunction(Type &x);
void SomeFunction(std::auto_ptr<Type> x);

void OtherFunction(const std::auto_ptr<Type> &x);

现在您必须先查看三个不同的文件,然后才能发现my_field设置为NULL。使用std::unique_ptr,您只需查看一个:

void MyClass::Method() {
    SomeFunction(std::move(my_field));
    OtherFunction(my_field);
}

只看这一个函数我知道它是错的,我不必弄清楚SomeFunction使用了哪个重载,我不必知道{{1}的类型是的。在明确和隐含之间我们需要有一个平衡点。在这种情况下,你无法明确告诉在C ++中移动和复制值之间的区别这一事实是一个问题,即右值引用,my_fieldstd::move等被添加到C ++中清楚的事情,他们是非常惊人的。

std::unique_ptr如此糟糕的另一个原因是因为它与容器的交互性很差。

auto_ptr

通常,许多模板与// This was a recipe for disaster std::vector<std::auto_ptr<Type> > my_vector; 的效果不佳,而不仅仅是容器。

答案 2 :(得分:2)

如果允许编译器自动推断移动std::unique_ptr等类型的语义,那么这样的代码就会破坏:

template<typename T> void simple_swap(T& a, T& b) {
    T tmp = a;
    a = b;
    b = tmp;
}

上述内容依据tmpa副本(因为它继续使用a作为赋值运算符的左侧) 。标准算法中有代码实际上需要容器值的临时副本。推断移动会破坏它们,导致在运行时崩溃。这就是std::auto_ptr被警告不要在STL容器中使用的原因。