为什么从 int totalRows= sheet.Rows.Length;
CellRange to;
for (int CurrentRow = 1; CurrentRow < totalRows+1; CurrentRow++)
{
string formula = string.Format("=SUM(F" + "{0}" + ",G" + "{0}" + ")", CurrentRow);
to = sheet.Range[CurrentRow,cl+1];
to.Formula = formula;
}
而不是移动构造函数返回时调用了复制构造函数?
bar
如果#include <iostream>
using namespace std;
class Alpha {
public:
Alpha() { cout << "ctor" << endl; }
Alpha(Alpha &) { cout << "copy ctor" << endl; }
Alpha(Alpha &&) { cout << "move ctor" << endl; }
Alpha &operator=(Alpha &) { cout << "copy asgn op" << endl; }
Alpha &operator=(Alpha &&) { cout << "move asgn op" << endl; }
};
Alpha foo(Alpha a) {
return a; // Move ctor is called (expected).
}
Alpha bar(Alpha &&a) {
return a; // Copy ctor is called (unexpected).
}
int main() {
Alpha a, b;
a = foo(a);
a = foo(Alpha());
a = bar(Alpha());
b = a;
return 0;
}
执行bar
,则行为符合预期。我不明白为什么调用return move(a)
是必要的,因为std::move
在返回时调用了移动构造函数。
答案 0 :(得分:4)
在这种情况下,有两件事需要理解:
a
中的bar(Alpha &&a)
是一个命名的右值参考;因此,被视为左值。a
仍是参考。第1部分
由于a
中的bar(Alpha &&a)
是一个命名的右值引用,因此将其视为左值。将命名的右值引用视为左值的动机是提供安全性。请考虑以下内容,
Alpha bar(Alpha &&a) {
baz(a);
qux(a);
return a;
}
如果baz(a)
将a
视为右值,则可以自由调用移动构造函数,qux(a)
可能无效。该标准通过将命名的右值引用视为左值来避免此问题。
第2部分
由于a
仍然是引用(并且可能引用bar
范围之外的对象),bar
在返回时会调用复制构造函数。这种行为背后的动机是提供安全。
<强>参考强>
答案 1 :(得分:1)
因此,标准委员会决定你必须这样做 明确关于任何命名变量的移动,无论其如何 参考类型
实际上“&amp;&amp;”已经指示放手了,当你做“返回”时,移动就足够安全了。
可能只是标准委员会的选择。
scott meyers的“有效的现代c ++”第25项,也对此进行了总结,没有给出太多解释。Alpha foo() {
Alpha a
return a; // RVO by decent compiler
}
Alpha foo(Alpha a) {
return a; // implicit std::move by compiler
}
Alpha bar(Alpha &&a) {
return a; // Copy ctor due to lvalue
}
Alpha bar(Alpha &&a) {
return std:move(a); // has to be explicit by developer
}
答案 2 :(得分:1)
当人们首次了解右值参考时,这是一个非常常见的错误。基本问题是类型和值类别之间的混淆。
int
是一种类型。 int&
是另一种类型。 int&&
是另一种类型。这些都是不同的类型。
左值和右值是称为值类别的东西。请在这里查看精彩的图表:What are rvalues, lvalues, xvalues, glvalues, and prvalues?。你可以看到除了左值和右值之外,我们还有prvalues和glvalues和xvalues,它们形成了各种维恩图的关系。
C ++有一些规则可以说各种类型的变量可以绑定到表达式。然而,表达式引用类型被丢弃(人们经常说表达式没有引用类型)。相反,表达式有一个值类别,它确定哪些变量可以绑定到它。
换句话说:右值引用和左值引用仅与赋值的左侧直接相关,即创建/绑定变量。在右侧,我们讨论的是表达式而不是变量,而rvalue / lvalue reference-ness仅与确定值类别的上下文相关。
一个非常简单的例子是简单地查看纯粹类型int
的事物。作为表达式的int
类型的变量是左值。但是,由评估返回int
的函数组成的表达式是rvalue。这对大多数人来说都是直观的;关键是要分离表达式的类型(甚至在引用被丢弃之前)和它的值类别。
这导致的是,即使int&&
类型的变量只能绑定到rvalues,也不意味着所有类型为int&&
的表达式,是 rvalues。事实上,正如http://en.cppreference.com/w/cpp/language/value_category中的规则所述,任何由命名变量组成的表达式总是左值,无论类型。
这就是为什么你需要std::move
来将rvalue引用传递给rvalue引用的后续函数。这是因为右值引用不绑定到其他右值引用。它们绑定到右值。如果你想获得移动构造函数,你需要给它一个rvalue来绑定,并且一个命名的右值引用不是右值。
std::move
是一个返回右值引用的函数。这种表达的价值范畴是什么?一个右值?不。它是一个xvalue。这基本上是一个右值,有一些额外的属性。
答案 3 :(得分:1)
在foo
和bar
中,表达式a
是左值。语句return a;
表示从初始化程序a
初始化返回值对象,并返回该对象。
两种情况之间的区别在于,根据a
声明为最内层封闭块内的非易失性自动对象或函数参数,执行此初始化的重载分辨率会有所不同。
适用于foo
而非bar
。 (在bar
中,a
被声明为参考)。因此return a;
中的foo
选择移动构造函数来初始化返回值,但return a;
中的bar
选择复制构造函数。
全文是C ++ 14 [class.copy] / 32:
当满足复制/移动操作的省略条件时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式为(可能是括号内的)时)id-expression命名一个对象,该对象具有在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的自动存储持续时间,首先执行重载决策以选择复制的构造函数,就好像指定了对象一样通过右值。如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行此两阶段重载决策。如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数。 - 后注]
其中&#34;符合复制/移动操作的标准&#34;是指[class.copy] /31.1:
- 在具有类返回类型的函数的return语句中,当表达式是非易失性自动对象的名称(函数或catch子句参数除外),其具有与函数相同的cv-unqualified类型返回类型,可以通过构造省略复制/移动操作 自动对象直接进入函数的返回值
请注意,这些文本将针对C ++ 17进行更改。