我有TestClass
个const&
成员变量。我从各个地方和自己的经验中知道,通过引用临时值来初始化此const&
是个坏主意。所以我很惊讶以下代码编译正常(使用gcc-4.9.1
,clang-3.5
和scan-build-3.5
测试)但无法正常运行。
class TestClass {
public:
// removing the "reference" would remove the temporary-problem
const std::string &d;
TestClass(const std::string &d)
: d(d) {
// "d" is a const-ref, cannot be changed at all... if it is assigned some
// temporary value it is mangled up...
}
};
int main() {
// NOTE: the variable "d" is a
// temporary, whose reference is not valid... what I don't get in the
// moment: why does no compiler warn me?
TestClass dut("d");
// and printing what we got:
std::cout << "beginning output:\n\n";
// this will silently abort the program (gcc-4.9.1) or be empty
// (clang-3.5) -- don't know whats going on here...
std::cout << "dut.d: '" << dut.d << "'\n";
std::cout << "\nthats it!\n";
return 0;
}
为什么两个编译器都没有在编译时警告我?另见这个ideone,还有更多的测试。
答案 0 :(得分:9)
没有警告没有冒犯:
本地const
引用会延长变量的生命周期。
该标准在§8.5.3/ 5 [dcl.init.ref]中指定了这种行为,这是关于参考声明的初始化者的部分。生命周期扩展不是通过函数参数传递的。 §12.2/ 5 [class.temporary]:
第二个上下文是引用绑定到临时的。引用绑定的临时值或临时值 完整对象到临时绑定的子对象 除以下规定外,在参考文件的生命周期内仍然存在。 临时绑定到构造函数中的引用成员 ctor-initializer(§12.6.2[class.base.init])一直持续到 构造函数退出。临时绑定到a中的引用参数 函数调用(§5.2.2[expr.call])一直持续到完成 包含调用的完整表达式。
您可以查看gotw-88,了解有关此主题的扩展且更具可读性的讨论。
你的代码是否正确?不,它的执行将导致未定义的行为。代码快照中的真正问题是,未定义的行为是由两个完美合法操作的混合引起的:构造函数的调用传递临时对象(其生命跨越构造函数块)和绑定构造函数定义中的引用。
编译器足够智能来检测这种爆炸性的声明组合,所以这就是你没有得到任何警告的原因。
答案 1 :(得分:3)
将const &
绑定到临时值是有效的,编译器将确保临时值至少与引用一样长。这允许您执行诸如将字符串文字传递到期望const std::string &
。
在您的情况下,您正在复制该引用,因此终身保证不再成立。你的构造函数退出,临时文件被销毁,你留下了对无效内存的引用。
答案 2 :(得分:2)
问题在于没有单一点可以发出警告。它只是构造函数的调用和它的实现的组合,导致未定义的行为。
如果你只考虑构造函数:
class TestClass {
public:
const std::string &d;
TestClass(const std::string &d)
: d(d)
{}
};
这里没有错,你有一个参考,你正在存储一个。以下是完全有效使用的示例:
class Widget {
std::string data;
TestClass test;
public:
Widget() : data("widget"), test(data)
{}
};
如果您只考虑呼叫网站:
//Declaration visible is:
TestClass(const std::string &d);
int main() {
TestClass dut("d");
}
这里,编译器不会“看到”(在一般情况下)构造函数的定义。想象一下另一种选择:
struct Gadget {
std::string d;
Gadget(cosnt std::string &d) : d(d) {}
};
int main()
{
Gadget g("d");
}
当然你也不想在这里发出警告。
总而言之,调用站点和构造函数实现都可以完全按原样使用。它只是导致问题的组合,但这种组合超出了编译器可以合理地用来发出警告的上下文。
答案 3 :(得分:1)
TestClass(const std::string &d1)
: d(d1) {
TestClass dut("d");
我想以下是逻辑上发生的事情: -
1)你的字符串文字("d")
将被隐式转换为std :: string(让我们给它起一个名字'x'
)。
2)因此,'x'
是一个临时的,在这里绑定d1
。此临时的生命周期延长至d1
的生命周期。虽然该字符串文字在程序结束前一直存在。
3)现在你正在制作'd' refer to 'd1'
。
4)在构造函数d1's
结束时,生命周期结束,d's
也是如此。
所有编译器都不是那么聪明地弄清楚这些小故障...