我有一个 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还有一些测试正在进行中。
没有任何违法的警告:
本地 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 有关此主题的扩展和更易阅读的讨论。
未定义的行为
那么你的代码是否正确?不,并且它的执行将导致未定义的行为。代码快照中的真正问题是未定义行为是由两者完美混合引起的 法律 operations:构造函数的调用,传递临时对象(其生命跨越构造函数块)以及构造函数定义中引用的绑定。
编译器不是 足够聪明 检测这种爆炸性的声明组合,所以这就是你没有得到任何警告的原因。
绑定一个 const &
临时是有效的,编译器将确保临时将至少与引用一样长。这允许您执行诸如将字符串文字传递到期望a的函数之类的操作 const std::string &
。
但是,在您的情况下,您正在复制该引用,因此终身保证不再成立。您的构造函数退出并且临时被销毁,并且您将获得对无效内存的引用。
问题在于没有单一点可以发出警告。它只是构造函数的调用和它的实现的组合导致未定义的行为。
如果你只考虑构造函数:
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");
}
当然你也不想在这里发出警告。
总而言之,调用站点和构造函数实现都是完全可用的。它只是导致问题的组合,但这种组合超出了编译器可以合理地用来发出警告的上下文。
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
。
所有编译器都不是那么聪明地弄清楚这些小问题......