ndk*_*pel 89 c++ foreach for-loop language-lawyer c++11
让我们假设我们有一个结构,用于保存3个带有一些成员函数的双精度数:
struct Vector {
double x, y, z;
// ...
Vector &negate() {
x = -x; y = -y; z = -z;
return *this;
}
Vector &normalize() {
double s = 1./sqrt(x*x+y*y+z*z);
x *= s; y *= s; z *= s;
return *this;
}
// ...
};
Run Code Online (Sandbox Code Playgroud)
这有点简单,但我相信你同意类似的代码.这些方法可以方便地链接,例如:
Vector v = ...;
v.normalize().negate();
Run Code Online (Sandbox Code Playgroud)
甚至:
Vector v = Vector{1., 2., 3.}.normalize().negate();
Run Code Online (Sandbox Code Playgroud)
现在,如果我们提供了begin()和end()函数,我们可以在new-style for循环中使用Vector,比如循环遍历3个坐标x,y和z(你无疑可以构造更多"有用"的例子通过用例如String替换Vector):
Vector v = ...;
for (double x : v) { ... }
Run Code Online (Sandbox Code Playgroud)
我们甚至可以这样做:
Vector v = ...;
for (double x : v.normalize().negate()) { ... }
Run Code Online (Sandbox Code Playgroud)
并且:
for (double x : Vector{1., 2., 3.}) { ... }
Run Code Online (Sandbox Code Playgroud)
但是,以下(在我看来)打破了:
for (double x : Vector{1., 2., 3.}.normalize()) { ... }
Run Code Online (Sandbox Code Playgroud)
虽然它似乎是前两个用法的逻辑组合,但我认为最后一个用法创建了一个悬空引用,而前两个用法完全没问题.
Nic*_*las 64
这是正确的,并广泛赞赏?
是的,你对事物的理解是正确的.
以上哪部分是"坏"部分,应该避免哪些?
坏部分是对函数返回的临时值进行l值引用,并将其绑定到r值引用.它和这一样糟糕:
auto &&t = Vector{1., 2., 3.}.normalize();
Run Code Online (Sandbox Code Playgroud)
临时Vector{1., 2., 3.}的生命周期无法扩展,因为编译器不知道normalize引用它的返回值.
是否可以通过更改基于范围的for循环的定义来改进语言,使得在for-expression中构造的临时数在循环的持续时间内存在?
这与C++的工作方式非常不一致.
它会阻止人们使用临时表达式或表达式的各种惰性评估方法制作某些陷阱吗?是.但它也需要特殊情况的编译器代码,并且混淆为什么它不能与其他表达式构造一起使用.
一个更合理的解决方案是通知编译器函数的返回值始终是this对它的引用,并且因此如果返回值绑定到临时扩展构造,那么它将扩展正确的临时.这是一个语言级的解决方案.
目前(如果编译器支持它),您可以使它normalize 无法在临时调用:
struct Vector {
double x, y, z;
// ...
Vector &normalize() & {
double s = 1./sqrt(x*x+y*y+z*z);
x *= s; y *= s; z *= s;
return *this;
}
Vector &normalize() && = delete;
};
Run Code Online (Sandbox Code Playgroud)
这将导致Vector{1., 2., 3.}.normalize()编译错误,同时v.normalize()将正常工作.显然你将无法做到这样的正确事情:
Vector t = Vector{1., 2., 3.}.normalize();
Run Code Online (Sandbox Code Playgroud)
但是你也不能做错误的事情.
或者,如注释中所建议,您可以使rvalue引用版本返回值而不是引用:
struct Vector {
double x, y, z;
// ...
Vector &normalize() & {
double s = 1./sqrt(x*x+y*y+z*z);
x *= s; y *= s; z *= s;
return *this;
}
Vector normalize() && {
Vector ret = *this;
ret.normalize();
return ret;
}
};
Run Code Online (Sandbox Code Playgroud)
如果Vector是具有要移动的实际资源的类型,则可以使用Vector ret = std::move(*this);.命名返回值优化使其在性能方面合理地最佳.
Dav*_*eas 25
for(double x:Vector {1.,2.,3.}.normalize()){...}
这不是语言的限制,而是代码的问题.表达式Vector{1., 2., 3.}创建一个临时表达式,但该normalize函数返回一个左值引用.因为表达式是左值,所以编译器假定该对象是活动的,但由于它是对临时对象的引用,因此在评估完整表达式后对象会死亡,因此您将留下悬空引用.
现在,如果您更改设计以按值返回新对象而不是对当前对象的引用,则不会出现问题,代码将按预期工作.