3

这是一个有点理论的问题,但是虽然我对 std::move 有一些基本的了解,但我仍然不确定它是否为语言提供了一些理论上无法用超级智能编译器实现的附加功能。我知道这样的代码:

{
  std::string s1="STL";
  std::string s2(std::move(s1));
  std::cout << s1 <<std::endl;
} 

是一种新的语义行为,而不仅仅是性能糖。:D 但是我想没有人会在执行 std::move(x) 之后使用 var x。同样对于仅可移动数据(std::unique_ptr<>,std::thread),如果类型被声明为可移动,编译器不能自动执行旧变量的移动构造和清除吗?同样,这意味着将在程序员背后生成更多代码(例如,现在您可以计算 cpyctor 和 movector 调用,而使用 automagic std::moving 您无法做到这一点)。

4

3 回答 3

7

不。

但是我想没有人会在做 std::move(x) 之后使用 var x

绝对不能保证。事实上,编译器不能std::move(x)自动使用的一个不错的部分原因是,它不能自动决定你是否打算这样做。这是明确定义好的行为。

此外,删除右值引用意味着编译器可以自动为您编写所有移动构造函数。这绝对不是真的。D 有类似的方案,但它完全失败了,因为在许多有用的情况下,编译器生成的“移动构造函数”无法正常工作,但您无法更改它。

它还会阻止完美转发,后者还有其他用途。

委员会犯了许多愚蠢的错误,但右值引用不是其中之一。

编辑:

考虑这样的事情:

int main() {
    std::unique_ptr<int> x = make_unique<int>();
    some_func_that_takes_ownership(x);
    int input = 0;
    std::cin >> input;
    if (input == 0)
        some_other_func(x);
}

哎呀。怎么办?您不能魔术在编译时知道“输入”的值。some_other_func如果和的尸体some_func_that_takes_ownership是未知的,这将是一个双重问题。这是停止问题 - 您无法证明x在 之后使用或不使用some_func_that_takes_ownership

D 失败。我答应了一个例子。基本上,在 D 中,“移动”是“二进制复制并且不破坏旧的”。不幸的是,考虑一个类,比如说,一个指向自身的指针——你会在大多数字符串类、大多数基于节点的容器、用于std::function、的设计boost::variant以及许多其他类似的方便值类型中找到它。指向内部缓冲区的指针将被复制,但哦,不!指向旧缓冲区,而不是新缓冲区。旧缓冲区被释放 - GG 你的程序。

于 2012-08-24T14:11:37.307 回答
2

这取决于您所说的“移动的作用”是什么意思。为了满足您的好奇心,我认为您希望被告知有关唯一性类型系统线性类型系统的存在。

这些类型系统在编译时(在类型系统中)强制一个值仅由一个位置引用,或者不进行新的引用。std::unique_ptr鉴于其相当弱的类型系统,它是 C++ 可以提​​供的最佳近似值。

假设我们有一个新的存储类说明符,称为uniqueref. 这类似于const,并指定该值具有单个唯一引用;没有其他人有价值。它将启用此功能:

int main()
{
    int* uniqueref x(new int); // only x has this reference

    // unique type feature: error, would no longer be unique
    auto y = x; 

    // linear type feature: okay, x not longer usable, z is now the unique owner
    auto z = uniquemove(x);

    // linear type feature: error: x is no longer usable
    *x = 5;
}

(注意到可以进行的巨大优化也很有趣,知道指针值实际上只通过该指针引用。restrict在这方面有点像 C99。)

就您所问的而言,由于我们现在可以说一个类型是唯一引用的,因此我们可以保证移动它是安全的。也就是说,移动操作最终是用户定义的,如果需要,可以做各种奇怪的事情,所以在当前的 C++ 中隐含地这样做是一个坏主意。

上面的所有内容显然都没有经过正式的考虑和指定,但应该让您了解这样的类型系统可能是什么样子。更一般地说,您可能需要一个Effect Type System

但是,是的,这些想法确实存在并且已经过正式研究。C++ 太成熟了,无法添加它们。

于 2012-08-24T18:52:21.487 回答
0

按照您建议的方式执行此操作比必要的要复杂得多:

std::string s1="STL";
std::string s2(s1);
std::cout << s1 <<std::endl;

在这种情况下,相当肯定是指副本。但是如果你去掉最后一行,s1它的生命周期基本上会在s2.

在引用计数的实现中,复制构造函数 forstd::string只会增加引用计数器,而析构函数将减少并在它变为零时删除。

所以顺序是

  1. (内联std::string::string(char const *)
    1. 确定字符串长度
    2. 分配内存
    3. 复制字符串
    4. 将引用计数器初始化为 1
    5. 初始化字符串对象中的指针
  2. (内联std::string::string(std::string const &)
    1. 递增引用计数器
    2. 将指针复制到字符串表示

现在编译器可以将其展平,只需将引用计数器初始化为 2 并将指针存储两次。公共子表达式消除然后找出s1s2保持相同的指针值,并将它们合并为一个。

简而言之,生成代码的唯一区别应该是引用计数器被初始化为 2。

于 2014-07-01T13:40:46.273 回答