绑定const&临时:没有编译器警告?

时间:2014-12-11 12:51:05

标签: c++ const-reference

我有TestClassconst&成员变量。我从各个地方和自己的经验中知道,通过引用临时值来初始化此const&是个坏主意。所以我很惊讶以下代码编译正常(使用gcc-4.9.1clang-3.5scan-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,还有更多的测试。

4 个答案:

答案 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也是如此。

所有编译器都不是那么聪明地弄清楚这些小故障...