11

我有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,正在进行更多测试。

4

4 回答 4

9

没有警告就没有冒犯:

局部const引用延长了变量的寿命。

该标准在第 8.5.3/5 节 [dcl.init.ref] 中指定了此类行为,这是关于引用声明的初始化程序的部分。生命周期扩展不能通过函数参数传递。§12.2/5 [class.temporary]:

第二个上下文是引用绑定到临时的。引用绑定到的临时对象或作为临时对象绑定的子对象的完整对象的临时对象将在引用的生命周期内持续存在,除非下面指定。临时绑定到构造函数的 ctor-initializer (§12.6.2 [class.base.init]) 中的引用成员将持续存在,直到构造函数退出。在函数调用(第 5.2.2 节 [expr.call])中临时绑定到引用参数会一直持续到包含调用的完整表达式完成为止。

您可以查看gotw-88以获取有关此主题的扩展且更具可读性的讨论。

未定义的行为

那么你的代码正确吗?不,它的执行将导致未定义的行为。代码快照中的真正问题是未定义行为是由两个完全合法的操作混合引起的:构造函数调用传递一个临时对象(其生命周期在构造函数块内)和构造函数中的引用绑定定义。

编译器不够聪明,无法检测到这种爆炸性的语句组合,所以这就是你没有得到任何警告的原因。

于 2014-12-11T12:59:16.977 回答
3

Binding a const & to a temporary is valid and the compiler will ensure that the temporary will live at least as long as the reference. This allows you to do things like pass string literals into functions expecting a const std::string &.

In your case however you are copying that reference and thus the lifetime guarantee no longer holds. Your constructor exits and the temporary is destroyed and you are left with a reference to invalid memory.

于 2014-12-11T12:56:14.407 回答
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");
}

当然,您也不想在这里发出警告。

总而言之,调用站点和构造函数实现都可以按原样完美使用。只有它们的组合会导致问题,但这种组合超出了编译器可以合理地用来发出警告的上下文。

于 2014-12-11T12:59:16.000 回答
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.

所有编译器都不是那么聪明地找出这些小故障......

于 2014-12-11T12:59:09.180 回答