如何延长表达式范围内临时变量的生命周期?
How do I extend the lifetime of a temporary in a ranged for expression?
我在使用范围 for 循环时遇到悬空引用。考虑以下 C++14 表达式(下面的完整示例程序):
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
它的输出是:
Wheel()
Wheel()
Bike()
~Bike() with 0 inflated wheels.
~Wheel()
~Wheel()
Wheel::inflate()
Wheel::inflate()
显然出了点问题。轮子的访问超出其生命周期,结果为 0,而不是预期的 2。
一个简单的解决方法是在 main
中为 Bike
引入一个变量。但是,我不控制main
或Wheel
中的代码。我只能更改结构 Bike
.
是否有任何方法可以通过仅更改 Bike
来修复此示例?
一个成功的解决方案要么在编译时失败,要么计数 2 个充气轮胎并且不接触任何超出其寿命的物体。
附录:编译就绪源码
#include <cstdlib>
#include <iostream>
#include <array>
#include <algorithm>
using std::cout;
using std::endl;
struct Wheel
{
Wheel() { cout << " Wheel()" << endl; }
~Wheel() { cout << "~Wheel()" << endl; }
void inflate() { inflated = true; cout << " Wheel::inflate()" << endl; }
bool inflated = false;
};
struct Bike
{
Bike() { cout << " Bike()" << endl; }
~Bike() {
cout << "~Bike() with " << std::count_if(wheels.begin(), wheels.end(),
[](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
}
std::array<Wheel, 2>& wheels_reference() { return wheels; }
std::array<Wheel, 2> wheels{Wheel(), Wheel()};
};
int main()
{
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
return EXIT_SUCCESS;
}
以下可怕的装置似乎满足所有条件:
#include <memory>
struct Bike
{
// Bike() { cout << " FakeBike()" << endl; }
// ~Bike() { cout << "~FakeBike()" << endl; }
struct RealBike;
struct Wrap {
std::shared_ptr<RealBike> parent;
auto begin() { return parent->wheels.begin(); }
auto end() { return parent->wheels.end(); }
};
struct RealBike {
RealBike() { cout << " Bike()" << endl; }
~RealBike() {
cout << "~Bike() with " << std::count_if(wheels.begin(), wheels.end(),
[](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
}
std::array<Wheel, 2> wheels;
};
std::shared_ptr<RealBike> real = std::make_shared<RealBike>();
Wrap wheels_reference() { return Wrap{real}; }
};
我不喜欢的是它需要将 std::array<Wheel, 2>
的所有 API 包装在 Wrap
中。
删除wheels_reference
的右值重载。
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2>& wheels_reference() && = delete;
这样你就不会 return 对临时成员的引用。
您对 Bike
class
的示例用法
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
然后将拒绝编译(clang 3.4 输出):
test.cpp:31:29: error: call to deleted member function 'wheels_reference'
for(auto& wheel: Bike().wheels_reference())
~~~~~~~^~~~~~~~~~~~~~~~
test.cpp:24:27: note: candidate function has been explicitly deleted
std::array<Wheel, 2>& wheels_reference() && = delete;
^
test.cpp:23:27: note: candidate function not viable: no known conversion from 'Bike' to 'Bike' for object argument
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
如果临时文件的生命周期是手动延长的,那么一切正常。
Bike&& bike = Bike();
for(auto& wheel: bike.wheels_reference())
wheel.inflate();
最好的解决方案是停止通过临时成员函数调用获取类型的成员。
如果 wheels_reference
是一个 non-member 函数,您可以像这样简单地声明它:
wheels_reference(Bike &bike);
由于 non-const 左值参数无法附加到临时变量,因此您将无法调用 wheels_reference(Bike())
。因为 wheels_reference
是一个成员函数,你只需要使用成员函数语法来表达同样的事情:
std::array<Wheel, 2>& wheels_reference() & //<--
{ return wheels; }
如果用户现在尝试调用 Bike().wheels_reference()
,编译器会报错。
您可以只添加 wheels_reference
成员函数的几个 cv-ref-qualified 重载:
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2> const & wheels_reference() const & { return wheels; }
std::array<Wheel, 2> wheels_reference() && { return std::move(wheels); }
std::array<Wheel, 2> wheels_reference() const && { return wheels; }
注意,如果对象是临时的(&&
情况),那么您应该 return 一个值(copy-constructed 来自数据成员,甚至更好 move-constructed 来自它) , 也没有引用。
所有四个重载都涵盖了所有 use-cases 可能。
我在使用范围 for 循环时遇到悬空引用。考虑以下 C++14 表达式(下面的完整示例程序):
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
它的输出是:
Wheel()
Wheel()
Bike()
~Bike() with 0 inflated wheels.
~Wheel()
~Wheel()
Wheel::inflate()
Wheel::inflate()
显然出了点问题。轮子的访问超出其生命周期,结果为 0,而不是预期的 2。
一个简单的解决方法是在 main
中为 Bike
引入一个变量。但是,我不控制main
或Wheel
中的代码。我只能更改结构 Bike
.
是否有任何方法可以通过仅更改 Bike
来修复此示例?
一个成功的解决方案要么在编译时失败,要么计数 2 个充气轮胎并且不接触任何超出其寿命的物体。
附录:编译就绪源码
#include <cstdlib>
#include <iostream>
#include <array>
#include <algorithm>
using std::cout;
using std::endl;
struct Wheel
{
Wheel() { cout << " Wheel()" << endl; }
~Wheel() { cout << "~Wheel()" << endl; }
void inflate() { inflated = true; cout << " Wheel::inflate()" << endl; }
bool inflated = false;
};
struct Bike
{
Bike() { cout << " Bike()" << endl; }
~Bike() {
cout << "~Bike() with " << std::count_if(wheels.begin(), wheels.end(),
[](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
}
std::array<Wheel, 2>& wheels_reference() { return wheels; }
std::array<Wheel, 2> wheels{Wheel(), Wheel()};
};
int main()
{
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
return EXIT_SUCCESS;
}
以下可怕的装置似乎满足所有条件:
#include <memory>
struct Bike
{
// Bike() { cout << " FakeBike()" << endl; }
// ~Bike() { cout << "~FakeBike()" << endl; }
struct RealBike;
struct Wrap {
std::shared_ptr<RealBike> parent;
auto begin() { return parent->wheels.begin(); }
auto end() { return parent->wheels.end(); }
};
struct RealBike {
RealBike() { cout << " Bike()" << endl; }
~RealBike() {
cout << "~Bike() with " << std::count_if(wheels.begin(), wheels.end(),
[](auto& w) { return w.inflated; }) << " inflated wheels." << endl;
}
std::array<Wheel, 2> wheels;
};
std::shared_ptr<RealBike> real = std::make_shared<RealBike>();
Wrap wheels_reference() { return Wrap{real}; }
};
我不喜欢的是它需要将 std::array<Wheel, 2>
的所有 API 包装在 Wrap
中。
删除wheels_reference
的右值重载。
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2>& wheels_reference() && = delete;
这样你就不会 return 对临时成员的引用。
您对 Bike
class
for(auto& wheel: Bike().wheels_reference())
wheel.inflate();
然后将拒绝编译(clang 3.4 输出):
test.cpp:31:29: error: call to deleted member function 'wheels_reference'
for(auto& wheel: Bike().wheels_reference())
~~~~~~~^~~~~~~~~~~~~~~~
test.cpp:24:27: note: candidate function has been explicitly deleted
std::array<Wheel, 2>& wheels_reference() && = delete;
^
test.cpp:23:27: note: candidate function not viable: no known conversion from 'Bike' to 'Bike' for object argument
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
如果临时文件的生命周期是手动延长的,那么一切正常。
Bike&& bike = Bike();
for(auto& wheel: bike.wheels_reference())
wheel.inflate();
最好的解决方案是停止通过临时成员函数调用获取类型的成员。
如果 wheels_reference
是一个 non-member 函数,您可以像这样简单地声明它:
wheels_reference(Bike &bike);
由于 non-const 左值参数无法附加到临时变量,因此您将无法调用 wheels_reference(Bike())
。因为 wheels_reference
是一个成员函数,你只需要使用成员函数语法来表达同样的事情:
std::array<Wheel, 2>& wheels_reference() & //<--
{ return wheels; }
如果用户现在尝试调用 Bike().wheels_reference()
,编译器会报错。
您可以只添加 wheels_reference
成员函数的几个 cv-ref-qualified 重载:
std::array<Wheel, 2>& wheels_reference() & { return wheels; }
std::array<Wheel, 2> const & wheels_reference() const & { return wheels; }
std::array<Wheel, 2> wheels_reference() && { return std::move(wheels); }
std::array<Wheel, 2> wheels_reference() const && { return wheels; }
注意,如果对象是临时的(&&
情况),那么您应该 return 一个值(copy-constructed 来自数据成员,甚至更好 move-constructed 来自它) , 也没有引用。
所有四个重载都涵盖了所有 use-cases 可能。