为什么静态成员变量不能很好地与三目运算符配合使用?

15

问题是这样的,我有一个静态类,其中包含多个用于获取输入的静态函数。该类包含一个私有静态成员变量来指示用户是否输入了任何信息。每个输入方法都会检查用户是否输入了任何信息,并相应地设置状态变量。我认为现在是使用三元运算符的好时机。不幸的是,我不能这样做,因为编译器不喜欢它。

我复制了问题,并简化了我的代码,使其尽可能容易理解。这不是我的原始代码。

这是我的头文件:

#include <iostream>

using namespace std;

class Test {
public:
    void go ();
private:
    static const int GOOD = 0;
    static const int BAD = 1;
};

这是我的实现方式,使用三元运算符:

#include "test.h"

void Test::go () {
    int num = 3;
    int localStatus;
    localStatus = (num > 2) ? GOOD : BAD;
}

这是主函数:

#include <iostream>
#include "test.h"

using namespace std;

int main () {
    Test test = Test();
    test.go();
    return 0;
}

当我尝试编译这段代码时,得到了以下错误信息:

test.o: In function `Test::go()':
test.cpp:(.text+0x17): undefined reference to `Test::GOOD'
test.cpp:(.text+0x1f): undefined reference to `Test::BAD'
collect2: ld returned 1 exit status

然而,如果我将这个替换成:

localStatus = (num > 2) ? GOOD : BAD;

用这个:

if (num > 2) {
    localStatus = GOOD;
} else {
    localStatus = BAD;
}

代码编译并按预期运行。是哪个C++规则或GCC边角情况导致了这种疯狂的结果?(我在Ubuntu 9.10上使用GCC 4.4.1。)

3个回答

19
根据C++标准,三元运算符构成一个单一的左值,将在运行时引用GOODBAD中的一个。左值到右值的转换不会立即应用于GOODBAD,因此需要定义GOODBAD
请参阅核心语言问题报告http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#712以获取更多信息。
作为解决方法,可以将显式转换应用于int(从而读取它们的值,执行左值到右值的转换),或使用读取值的操作符,例如+
localStatus = (num > 2) ? +GOOD : +BAD;

标准在哪里区分这两种情况?难道它们都“使用”GOOD,因此需要一个定义吗?我认为gcc足够聪明,在将lvalue GOOD用作赋值操作符的RHS时避免了外部链接(因此立即进行了lvaue-rvalue转换),但在三元运算符中使用lvalue GOOD时尚未完成,但我准备相信我可能是错的。 - Steve Jessop
@Steve 如果你执行 +GOOD 或者 int a = GOOD,那么你会在左值 GOOD 上进行左值到右值的转换("直接" - 参见3.2p2,在这种情况下,你不会 "使用" 变量)。如果你执行 int a = x ? a : b;,那么你会在三元运算符产生的左值上进行左值到右值的转换,而不是在 ab 产生的左值上进行。 - Johannes Schaub - litb
啊,你对詹姆斯的回答评论了我的问题。它们是通过在C++03之后但在2008年提出该缺陷之前插入草稿文本来区分的。我认为当前标准做到了我所说的。我的印刷本和2003(E)的PDF都说:“如果其名称出现在可能评估的表达式中,则使用对象或非重载函数。”没有例外。 - Steve Jessop

5
class Test {
    static const int GOOD = 0;
    static const int BAD = 1;
};

这些只是声明,而不是定义。您需要在类的定义之外,在一个.cpp文件中提供静态成员变量的定义:

const int Test::GOOD;
const int Test::BAD;

另外,对于整数常量,使用 enum 通常更加方便:

class Test {
    enum { 
        GOOD = 0,
        BAD = 1 
    };
};

2
为什么缺少定义会影响三元运算符,但不会影响标准赋值运算符? - Evan Kroske
这并不是那么简单的。请看我的回答。 - TonyK
@Evan:你说的是哪个赋值运算符?是将值分配给GOOD和BAD的那个吗? - Saurabh Manchanda
1
@Evan:我不是100%确定,但我会试着回答一下:当你使用直接赋值时,GOODBAD只被直接用作rvalue。然而,当你使用条件运算符时,条件表达式num > 2 ? GOOD : BAD本身就是一个lvalue表达式,所以在选择GOODBAD之后才正式进行lvalue-to-rvalue转换,因此需要实际的对象(带有定义)。 - James McNellis

1

你的代码看起来没问题。而且ideone也显示是正确的,可以看this link。但是这是在gcc-4.3.4下的情况。然而,我的gcc-4.4.0不接受它。所以无论原因如何,都不是很明显。

编辑后添加: 下面这个变量可以在gcc-4.4.0下编译:

int localStatus = 42 ? GOOD : BAD;

提醒:以下代码无法编译:

int localStatus = (num == 42) ? GOOD : BAD;

所以某人在某个地方搞砸了。


我不确定是否有人在某个地方搞砸了。我相信,在第一种情况下唯一发生的事情是表达式被优化掉了。但真正的问题仍然存在。 - Petr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接