enum vs constexpr用于类中的实际静态常量

时间:2014-04-04 16:02:16

标签: c++ c++11 constexpr

首先让我说明我的意图。在旧版(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

导致这种情况的情况似乎是在使用const SOME_VALUE的引用时,这在C ++标准库代码中经常发生(请参阅本问题底部的示例)。顺便说一句,我使用gcc 4.7.2作为我的编译器。

由于这种困境,我不得不恢复将SOME_VALUE定义为枚举(即旧学校),以避免为某些内容添加cpp文件的定义,但不是全部我的静态constexpr成员变量。是不是有一些方法告诉编译器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;

6 个答案:

答案 0 :(得分:9)

这里有三个选项:

  1. 如果您的类是模板,那么将静态成员的定义放在标题本身中。编译器需要仅在多个翻译单元中将其标识为一个定义(参见[basic.def.odr] / 5)

  2. 如果您的课程是非模板,则可以轻松将其放入源文件

  3. 或者声明constexpr静态成员函数getSomeValue():

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    

答案 1 :(得分:8)

对于记录,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)。 [...]

这与引入同一事物inline static data members的非constexpr版本的机制相同。

struct A {
 static inline int n = 5; // definition (illegal in C++ 2014)
}; 

inline int A::n; // illegal

答案 2 :(得分:2)

我会选择枚举类:

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 << "red\n"; break;
    case Color::GREEN : std::cout << "green\n"; break;
    case Color::BLUE : std::cout << "blue\n"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21

答案 3 :(得分:1)

来自C ++标准N3797 S3.5 / 2-3

  

当一个名称可能表示与另一个范围内的声明所引入的名称相同的对象,引用,函数,类型,模板,名称空间或值时,该名称具有链接:

     

- 当名称具有外部链接时,其表示的实体可以通过其他翻译单位的范围或同一翻译单位的其他范围中的名称来引用。

     

- 当名称具有内部链接时,其表示的实体可以通过同一翻译单元中其他范围的名称来引用。

     

- 当名称没有链接时,它所代表的实体不能被其他范围的名称引用。

     

具有命名空间范围(3.3.6)的名称具有内部链接(如果它是

的名称)      

- 显式声明为static的变量,函数或函数模板;或者,

     

- 一个非易失性变量,显式声明为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的引用相关联,并且在其他地方不可见。

显然,第一个是声明,而不是定义。它需要在同一翻译单元中定义。如果海湾合作委员会这样说而MSVC没有,那么MSVC是错误的。

为了更换枚举,2号应该可以正常工作。它仍然没有static关键字的内部链接。

[回复评论时编辑]

答案 4 :(得分:1)

如今,首选方式是:

enum class : int C { SOME_VALUE = 5 };

答案 5 :(得分:-1)

你可以这样做

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