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



概念问题

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

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则具有相反的一面。 如上所示的示例,当您查看行-(1) 和行-(2) 时,您注意到违反了一些标准规则,因为 std::unique_ptr 没有(删除)定义复制构造函数和复制可分配运算符,但为了避免编译错误,我们必须使用std::move函数。

问题

为什么现代C++编译器不能自动生成指令来在第(1) 行(2)行中的唯一指针之间移动资源?因为我们知道 Unique 指针有意为此而设计。为什么我们应该显式使用 std::move 来指示机器移动资源的所有权?

std::unique_ptr 只有类模板。我们知道,但是第 1 行和第 -2 行中解决的情况在编译器抱怨复制时出现问题unique_pointers不允许(删除函数).为什么我们有这些错误为什么 C++ 标准和编译器供应商不能覆盖这个概念?

唯一指针故意设计用于在传递其所有权的同时移动资源,当我们将其作为函数/构造函数参数传递或分配给另一个唯一指针时,它从概念上讲应该移动具有所有权的资源,而不是其他任何东西,但是为什么我们应该使用 std::move 将编译器传达给实际移动,为什么我们不能自由地调用 line-(1) 和 line-(2)?(而智能编译器为我们生成唯一指针之间的自动移动操作,除非有常量或非常量引用传递)。

(对不起,描述冗长,英语蹩脚)谢谢。

unique_ptr

对于在超出范围时自动为您释放内存非常有用uobj。这就是它的工作。因此,由于它有 1 个指针,因此它必须释放,因此它必须是唯一的,因此它的名字:unique_ptr

当你做这样的事情时:

std::unique_ptr<int> uobjAlt = uobj;

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

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

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

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,我也不必知道my_field的类型是什么。 在明确和隐含之间,我们肯定需要有一个平衡。 在这种情况下,您无法明确区分在C++中移动和复制值之间的区别,这是一个问题,因此将右值引用、std::movestd::unique_ptr等添加到C++以清除问题,它们非常惊人。

auto_ptr如此糟糕的另一个原因是因为它与容器的交互效果很差。

// This was a recipe for disaster
std::vector<std::auto_ptr<Type> > my_vector;

一般来说,许多模板在auto_ptr上效果不佳,而不仅仅是容器。

如果允许编译器自动推断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容器中使用的原因。

相关内容

最新更新