如果对象在下一步被破坏,为什么不自动移动?

Why not auto move if object is destroyed in next step?

如果一个函数 return 的值是这样的:

std::string foo() {
  std::string ret {"Test"};
  return ret;
}

允许编译器移动 ret,因为它不再被使用。这不适用于这样的情况:

void foo (std::string str) {
  // do sth. with str
}

int main() {
  std::string a {"Test"};
  foo(a);
}

虽然 a 显然不再需要了,因为它在下一步中被销毁了,你必须做:

int main() {
  std::string a {"Test"};
  foo(std::move(a));
}

为什么?在我看来,这是不必要的复杂,因为右值和移动语义很难理解,尤其是对于初学者。因此,如果您不必在标准情况下关心但无论如何都可以从移动语义中受益(例如 return 值和临时值),那就太好了。必须查看 class 定义以发现 class 是否启用移动并从 std::move 中受益(或无论如何在希望它有时会有所帮助。如果您使用现有代码,它也很容易出错:

int main() {
  std::string a {"Test"};
  foo(std::move(a));

  // [...] 100 lines of code
  // new line:
  foo(a); // Ups!
}

编译器更清楚一个对象是否不再使用。 std::move 到处都是冗长的,降低了可读性。

因为除非标准允许,否则编译器无法进行改变程序行为的优化。 return 在某些情况下允许优化,但不允许对方法调用进行此优化。通过改变行为,它会跳过调用可能有副作用的复制构造函数和析构函数(它们不需要是纯的)但是通过跳过它们,这些副作用不会发生,因此行为会改变。

(请注意,这在很大程度上取决于您尝试传递的内容,在这种情况下,STL 实现。在编译时所有代码都可用的情况下,编译器可能会确定复制构造函数和析构函数都是纯并优化它们。)

虽然允许编译器在您的第一个代码段中移动 ret,但它也可能会执行 copy/move 省略并将其直接构建到调用程序的堆栈中。 这就是为什么不建议这样写函数的原因:

std::string foo() {
    auto ret = std::string("Test");

    return std::move(ret);
}

现在对于第二个片段,您的字符串 a 是一个左值。移动语义仅适用于右值引用,它通过返回一个临时的、未命名的对象或转换一个左值来获得。后者正是 std::move 所做的。

std::string GetString();

auto s = GetString();

// s is a lvalue, use std::move to cast it to rvalue-ref to force move semantics
foo(s);

// GetString returns a temporary object, which is a rvalue-ref and move semantics apply automatically
foo(GetString());

一个对象在给定点之后不会被使用并不明显。 例如,查看以下代码变体:

struct Bar {
  ~Bar() { std::cout << str.size() << std::endl; }
  std::string& str;
}

Bar make_bar(std::string& str) {
  return Bar{ str };
}

void foo (std::string str) {
  // do sth. with str
}

int main() {
  std::string a {"Test"};
  Bar b = make_bar(a);
  foo(std::move(a));
}

这段代码会中断,因为字符串 a 被移动操作置于无效状态,但 Bar 持有对它的引用,并会在它被销毁时尝试使用它,这发生了 foo 调用之后。

如果 make_bar 是在外部程序集中定义的(例如 DLL/so),编译器在编译 Bar b = make_bar(a); 时无法判断 b 是否持有对a 与否。因此,即使 foo(a)a 的最后一次使用,也不意味着使用移动语义是安全的,因为其他一些对象可能因此持有对 a 的引用 之前 个说明。

只有才能知道你是否可以使用移动语义,通过查看你调用的函数的规范。

另一方面,您始终可以在 return 情况下使用移动语义,因为该对象无论如何都会超出范围,这意味着任何持有对它的引用的对象都将导致未定义的行为,无论的移动语义。 顺便说一下,由于复制省略,你甚至不需要在那里移动语义。

都是你定义的"Destroyed"的总结? std::string对自毁没有特殊作用,只是释放隐藏在里面的char数组。

如果我的析构函数做了一些特别的事情怎么办?例如 - 做一些重要的日志记录?然后通过简单的 "moving it because it's not needed anymore" 我错过了析构函数可能做的一些特殊行为。