枚举与 constexpr 用于类内的实际静态常量
enum vs constexpr for actual static constants inside classes
让我先陈述一下我的意图。在过去(C++)时代,我们将拥有如下代码:
class C
{
public:
enum {SOME_VALUE=27};
};
然后我们可以在整个代码中使用SOME_VALUE
作为编译时常量,无论编译器在哪里看到C::SOME_VALUE
,它都会插入文字 27。
现在,将该代码更改为以下内容似乎更容易接受:
class C
{
public:
static constexpr int SOME_VALUE=27;
};
这看起来更干净,为SOME_VALUE
提供了一个定义良好的类型,并且似乎是截至 C++11 的首选方法。(至少对我来说是不可预见的)问题是,这也会导致需要将SOME_VALUE
外部的情况。也就是说,在某个 cpp 文件中的某个地方,我们需要添加:
constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage
导致这种情况的情况似乎是使用对SOME_VALUE
的常量引用,这在标准库代码中经常发生C++(请参阅本问题底部的示例)。顺便说一下,我正在使用 gcc 4.7.2 作为我的编译器。
由于这种困境,我被迫恢复将SOME_VALUE
定义为枚举(即老派),以避免必须为某些(但不是所有)静态 constexpr 成员变量向 cpp 文件添加定义。难道没有办法告诉编译器,constexpr int SOME_VALUE=27
意味着SOME_VALUE
应该只被视为编译时常量,而不是具有外部链接的对象吗?如果看到与它一起使用的 const 引用,请创建一个临时引用。如果您看到它的地址被占用,如果需要,请生成编译时错误,因为它是一个编译时常量,仅此而已。
以下是一些看似良性的示例代码,导致我们需要在 cpp 文件中添加 SOME_VALUE
的定义(再次使用 gcc 4.7.2 进行测试):
#include <vector>
class C
{
public:
static constexpr int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE); // Will cause an undefined reference error
// at link time, because the compiler isn't smart
// enough to treat C::SOME_VALUE as the literal 5
// even though it's obvious at compile time
}
将以下行添加到文件范围内的代码将解决此错误:
constexpr int C::SOME_VALUE;
作为记录,static constexpr
版本将像您在 C++17 中预期的那样工作。从N4618附录D.1 [depr.static_constexpr]:
D.1 重新声明
static constexpr
数据成员 [depr.static_constexpr]为了与先前的C++国际标准兼容,可以在类外部冗余地重新声明
constexpr
静态数据成员,而不使用初始值设定项。此用法已弃用。[示例:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
—结束示例]
允许这样做的相关标准文本是 N4618 9.2.3 [class.static.data]/3:
[...]内联静态数据成员可以在类定义中定义,并且可以指定大括号或等于初始值设定项。如果使用
constexpr
说明符声明成员,则可以在没有初始值设定项的情况下在命名空间范围内重新声明该成员(此用法已弃用;请参阅 D.1)。[...]
这带有引入同一事物的非constexpr
版本的相同机制,即内联静态数据成员。
struct A {
static inline int n = 5; // definition (illegal in C++ 2014)
};
inline int A::n; // illegal
这里有三个选项:
-
如果你的类是模板,那么把静态成员的定义放在标题本身中。编译器需要仅跨多个翻译单元将其标识为一个定义(请参阅 [basic.def.odr]/5)
-
如果您的类是非模板的,您可以轻松地将其放入源文件中
-
或者声明 constexpr 静态成员函数 getSomeValue():
class C { public: static constexpr int getSomeValue() { return 27; } };
来自C++标准 N3797 S3.5/2-3
当名称可能表示与另一个作用域中的声明引入的名称相同的对象、引用、函数、类型、模板、命名空间或值时,它被称为具有链接:
— 当一个名称具有外部链接时,它所表示的实体可以用其他翻译单元的范围或同一翻译单元的其他范围的名称来引用。
— 当一个名称具有内部链接时,它所表示的实体可以用同一翻译单元中其他范围的名称来引用。
— 当一个名称没有链接时,它所表示的实体不能被其他范围的名称引用。
具有命名空间作用域 (3.3.6) 的名称具有内部链接,如果它是
— 显式声明为静态的变量、函数或函数模板;或者,
— 明确声明const 或 constexpr 的非易失性变量,既未明确声明 extern,也未声明具有外部链接;或
— 匿名工会的数据成员。
我的解读是以下代码:
public:
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
SOME_VALUE
的所有 4 个实例都具有内部链接。它们应与同一翻译单元中对SOME_VALUE
的引用链接,而不是在其他地方可见。
显然,第一个是声明而不是定义。它需要在同一翻译单元中定义。如果GCC这么说而MSVC没有,那么MSVC就错了。
为了替换枚举,数字 2 应该可以正常工作。它仍然具有没有static
关键字的内部链接。
[根据评论编辑]
我会选择枚举类:
http://en.cppreference.com/w/cpp/language/enum
http://www.stroustrup.com/C++11FAQ.html#enum
从第一个链接:
enum class Color { RED, GREEN=20, BLUE};
Color r = Color::BLUE;
switch(r) {
case Color::RED : std::cout << "redn"; break;
case Color::GREEN : std::cout << "greenn"; break;
case Color::BLUE : std::cout << "bluen"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21
<</div>
div class="answers">你可以这样做
class C
{
public:
static const int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE);
}
这甚至不是C++11,只是C++98
如今,首选方法是:
enum class : int C { SOME_VALUE = 5 };
- 私有类型的静态常量成员
- 分离一个静态常量 std::thread?
- 从另一个静态常量数组初始化静态常量数组(只需少量计算)
- 我可以在运行时重新定义在 OpenCascade/OCCT 标头中定义的 c++ 静态常量吗?
- 如何为静态常量模板化专用整数值分配存储
- 使用什么代替"静态常量 TCHAR *"
- C++ 模板中的静态常量初始化顺序
- 如何在编译时解析静态常量 std::string?
- 关于静态常量数据模因的声明和定义的混淆
- 将 static_cast<int>(-15) 分配给静态常量字符类型变量
- 为什么在第二类中使用静态常量会在第一类中给出编译器错误?
- 静态常量与常量局部变量,哪一个性能更好
- 如何在模板类中设置静态常量变量
- public:静态常量字符串声明/初始化问题
- 有没有办法声明一个公共静态常量,该常量将使用 constexpr 在源文件中定义(有什么区别)?
- 对静态常量积分类型的未定义引用
- 全局变量中的静态常量与常量
- c++ 类中的静态常量变量和常量变量在存储方面是否有区别
- 避免在静态常量类上定义但不使用
- 指向静态常量对象的共享指针?