【问题标题】:How to declare a static const char* in your header file?如何在头文件中声明静态 const char*?
【发布时间】:2010-12-10 23:34:53
【问题描述】:

我想在我的头文件中定义一个常量 char* 以供我的 .cpp 文件使用。所以我试过这个:

private:
    static const char *SOMETHING = "sommething";

这给我带来了以下编译器错误:

错误 C2864:“SomeClass::SOMETHING”: 只有静态 const 整数数据 成员可以在一个 类

我是 C++ 新手。这里发生了什么?为什么这是非法的?你怎么能这样做呢?

【问题讨论】:

  • 您应该改用“static const char* const SOMETHING”,除非您真的希望能够在运行时重新分配 SOMETHING 以指向其他东西。

标签: c++ constants


【解决方案1】:

您需要在翻译单元中定义静态变量,除非它们是整数类型。

在您的标题中:

private:
    static const char *SOMETHING;
    static const int MyInt = 8; // would be ok

在.cpp文件中:

const char *YourClass::SOMETHING = "something";

C++ 标准,9.4.2/4:

如果静态数据成员是 const 整数或 const 枚举类型, 它在类中的声明 定义可以指定一个 常量初始化器,它应该是一个 整数常量表达式。在那里面 情况下,该成员可以出现在 整数常量表达式 它的范围。该成员仍应为 如果是,则在命名空间范围内定义 在程序和命名空间中使用 范围定义不应包含 初始化器。

【讨论】:

  • 所以我不可能在头文件中分配这个?我可以用其他数据类型来做到这一点,比如 DWORD。这是为什么呢?
  • 因为指针不是整数类型。 Integral 仅包含内置函数。
  • 为什么会这样?是什么限制导致这个限制在 C++ 中受到限制?
  • 具体来说,DWORD 是整数类型。非整数类型包括(例如)指针和浮点类型。
  • DWORD 是 Windows SDK 中定义的众多 typedef 之一。它转换为unsigned int,这是一个整数类型。
【解决方案2】:

错误是您无法在类中初始化static const char*。你只能在那里初始化整数变量。

需要在类中声明成员变量,然后在类外初始化:

// 头文件

class Foo {
    static const char *SOMETHING;
    // rest of class
};

// cpp文件

const char *Foo::SOMETHING = "sommething";

如果这看起来很烦人,可以认为是因为初始化只能出现在一个翻译单元中。如果它在类定义中,通常会包含在多个文件中。常量整数是一种特殊情况(这意味着错误消息可能并不像它可能的那样清晰),编译器可以有效地将变量的使用替换为整数值。

相比之下,char* 变量指向内存中的一个实际对象,这是真正存在所必需的,它是使对象存在的定义(包括初始化)。 “一个定义规则”意味着您因此不想将其放在标题中,因为包括该标题在内的所有翻译单元都将包含该定义。即使字符串在两者中包含相同的字符,它们也无法链接在一起,因为根据当前的 C++ 规则,您已经定义了两个具有相同名称的不同对象,这是不合法的。他们碰巧有相同的字符这一事实并不使其合法。

【讨论】:

  • cpp 文件中不需要另一个“静态”。
【解决方案3】:

C++ 标准允许的常量初始化器仅用于整数或枚举类型。详见 9.4.2/4:

如果静态数据成员是 const 整数或 const 枚举类型,则它在类中的声明 定义可以指定一个常量初始化器,它应该是一个整数常量表达式(5.19)。在那里面 在这种情况下,成员可以出现在整型常量表达式中。该成员仍应以名称定义- 空间范围(如果在程序中使用)并且命名空间范围定义不应包含初始化器。

还有 9.4.2/7:

静态数据成员的初始化和销毁​​与非本地对象(3.6.2、3.6.3)完全相同。

所以你应该在cpp文件的某个地方写:

const char* SomeClass::SOMETHING = "sommething";

【讨论】:

    【解决方案4】:

    如果您使用的是 Visual C++,则可以使用指向链接器的提示以不可移植的方式执行此操作...

    // In foo.h...
    
    class Foo
    {
    public:
       static const char *Bar;
    };
    
    // Still in foo.h; doesn't need to be in a .cpp file...
    
    __declspec(selectany)
    const char *Foo::Bar = "Blah";
    

    __declspec(selectany) 表示即使Foo::Bar 将在多个目标文件中声明,链接器也只会选择一个。

    请记住,这仅适用于 Microsoft 工具链。不要指望它是可移植的。

    【讨论】:

      【解决方案5】:

      有一个技巧可以用于模板来提供仅 H 文件的常量。

      (注意,这是一个丑陋的例子,但至少在 g++ 4.6.1 中逐字显示。)

      (values.hpp 文件)

      #include <string>
      
      template<int dummy>
      class tValues
      {
      public:
         static const char* myValue;
      };
      
      template <int dummy> const char* tValues<dummy>::myValue = "This is a value";
      
      typedef tValues<0> Values;
      
      std::string otherCompUnit(); // test from other compilation unit
      

      (main.cpp)

      #include <iostream>
      #include "values.hpp"
      
      int main()
      {
         std::cout << "from main: " << Values::myValue << std::endl;
         std::cout << "from other: " << otherCompUnit() << std::endl;
      }
      

      (其他.cpp)

      #include "values.hpp"
      
      std::string otherCompUnit () {
         return std::string(Values::myValue);
      }
      

      编译(例如 g++ -o main main.cpp other.cpp && ./main)并查看两个编译单元引用了头文件中声明的相同常量:

      from main: This is a value
      from other: This is a value
      

      在 MSVC 中,您可以改为使用 __declspec(selectany)

      例如:

      __declspec(selectany) const char* data = "My data";
      

      【讨论】:

      • 模板化技巧不正确。提供显式专业化的多个定义是非法的。如果编译器无法捕获它,这可能会起作用,但这仍然违反了 ODR。
      • 实际上,模板技巧的正确实现会使用非特化声明。
      • MS 编译器不遵循所有标准的另一种情况?我不得不承认我从未在 GCC 上尝试过。
      • Torlack,违反 ODR 会导致未定义的行为。 “按预期工作”是一种可能的行为。在 UB 方面,遵循标准是不可能的。
      • @AndreyT:我无法从表面上理解你的陈述。在 14.7 [temp.spec]/5 中的当前标准中,它确实声明对于同一参数列表在程序中多次专门化模板是一个错误,所以我必须同意这一点。现在,我的问题在于,上面的代码似乎与在两个编译单元中包含向量并在两个编译单元中都使用 std::vector&lt;bool&gt; 没有太大区别。这些符号将在两个 C.U. 中编译。但这并不是真正违反 ODR,有一个定义,无论它是否恰好在多个编译中......
      【解决方案6】:

      回答 OP 关于为什么只允许使用整数类型的问题。

      当一个对象用作左值(即作为在存储中具有地址的东西)时,它必须满足“单一定义规则”(ODR),即它必须在一个且只有一个翻译单元中定义。编译器不能也不会决定在哪个翻译单元中定义该对象。这是您的责任。通过在某处定义该对象,您不仅仅是在定义它,您实际上是在告诉编译器您要在here中定义它,具体在this中翻译单元。

      同时,在 C++ 语言中,整数常量具有特殊的地位。它们可以形成整数常量表达式 (ICE)。在 ICE 中,整数常量用作普通 ,而不是 对象(即,这种整数值是否在存储中具有地址无关紧要)。事实上,ICE 是在编译时进行评估的。为了促进这种整数常量的使用,它们的值必须是全局可见的。并且常量本身并不真正需要在存储中的实际位置。因为这个整数常量得到了特殊处理:允许在头文件中包含它们的初始化器,并且放宽了提供定义的要求(首先是事实上的,然后是法律上的)。

      其他常量类型没有这样的属性。其他常量类型实际上总是用作左值(或者至少不能参与 ICE 或任何类似于 ICE 的东西),这意味着它们需要定义。其余的如下。

      【讨论】:

      • @Mooing Duck:ODR 的全文相当丰富。它有很多“例外”和“非例外”,甚至不局限于“模板”。我的回答仅限于处理整数常量的 ODR 的一小部分相关部分。我看不出您的“模板除外”评论与此处的相关性(我什至不明白您的评论到底是什么意思。)
      • 没错,我给了 +1,因为这是一个很好的答案,但模板类(据我所知)是所有 ODR 规则的例外。我只是想我会把它扔在那里。
      【解决方案7】:

      要回答为什么这个问题,整数类型的特殊之处在于它们不是对已分配对象的引用,而是对被复制和复制的值。这只是在定义语言时做出的实现决策,即以尽可能高效和“内联”的方式处理对象系统之外的值。

      这并不能完全解释为什么它们被允许作为类型中的初始化器,但将其视为本质上的 #define,然后它将作为类型的一部分而不是对象的一部分有意义。

      【讨论】:

        【解决方案8】:
        class A{
        public:
           static const char* SOMETHING() { return "something"; }
        };
        

        我一直都这样做——尤其是对于昂贵的 const 默认参数。

        class A{
           static
           const expensive_to_construct&
           default_expensive_to_construct(){
              static const expensive_to_construct xp2c(whatever is needed);
              return xp2c;
           }
        };
        

        【讨论】:

          【解决方案9】:

          使用 C++11,您可以使用 constexpr 关键字并在标题中写入:

          private:
              static constexpr const char* SOMETHING = "something";
          


          注意事项:

          • constexpr 使SOMETHING 成为常​​量指针,因此您无法写入

            SOMETHING = "something different";
            

            稍后。

          • 根据您的编译器,您可能还需要在 .cpp 文件中编写显式定义:

            constexpr const char* MyClass::SOMETHING;
            

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-12-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-05
          • 2017-06-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多