静态常量字符串(类成员)


444

我想要一个类的私有静态常量(在这种情况下是形状工厂)。

我想要些类似的东西。

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

不幸的是,我从C ++(g ++)编译器中收到各种错误,例如:

ISO C ++禁止初始化成员“ RECTANGLE”

非整数类型'std :: string'的静态数据成员的无效的类内初始化

错误:将“ RECTANGLE”设为静态

这告诉我,这种成员设计不符合该标准。您如何在没有使用#define指令的情况下拥有私有文字常量(或可能是公共常量)(我想避免数据全局性的丑陋!)

任何帮助表示赞赏。


15
感谢您的所有精彩回答!SO万岁!
磅。

有人可以告诉我什么是“积分”类型吗?非常感谢你。

1
整数类型是指代表整数的类型。见publib.boulder.ibm.com/infocenter/comphelp/v8v101/...
羊咩咩

工厂中的私有静态字符串不是一个好的解决方案-考虑到工厂客户将必须知道支持哪些形状,因此与其将其保留在私有静态中,不如将其放在单独的命名空间中,作为静态const std :: string RECTANGLE =“ Rectangle ”。
LukeCodeBaker

如果您的课程是模板课程,请参见stackoverflow.com/q/3229883/52074
Trevor Boyd Smith,

Answers:


467

您必须在类定义之外定义静态成员,然后在其中提供初始化程序。

第一

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

然后

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

您最初尝试使用的语法(类定义内的初始化程序)仅适用于整数和枚举类型。


从C ++ 17开始,您还有另一个选择,与原始声明非常相似:内联变量

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

无需其他定义。

或者,const也可以constexpr在此变体中声明它。inline由于constexpr隐含,不再需要显式inline


8
另外,如果不需要使用STL字符串,则最好定义一个const char *。(更少的开销)
KSchmidt

50
我不确定总会减少开销-这取决于使用情况。如果要将此成员作为参数传递给采用const string&的函数,则将为每个调用临时创建,而不是在初始化期间创建一个字符串对象。恕我直言,创建静态字符串对象的开销是可以忽略的。
塔德乌斯·科佩克

23
我也想一直使用std :: string。开销可以忽略不计,但是您拥有更多选择,并且写一些像“ magic” == A :: RECTANGLE之类的愚蠢的东西的可能性也就很小了
Matthieu M.

9
char const*有善良的所有动态初始化完成之前,它被初始化。因此,在任何对象的构造函数中,您都可以依赖于RECTANGLE已初始化的协议。
Johannes Schaub-litb

8
@cirosantilli:因为从一开始C ++初始化程序就是定义的一部分,而不是声明。类中的数据成员声明就是这样:一个声明。(另一方面,对于const整型和枚举成员,以及C ++ 11中的常量类型的const成员,都做了一个例外。)
AnT 2013年

153

在C ++ 11中,您现在可以执行以下操作:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

30
不幸的是,该解决方案不适用于std :: string。
HelloWorld

2
注意1.这仅适用于文字,而2.不符合标准,尽管Gnu / GCC遵守罚款规定,但其他编译器将引发错误。定义必须真实。
ManuelSchneid3r 2015年

2
@ ManuelSchneid3r“不符合标准”到底是什么?在我看来,这像是标准的C ++ 11 大括号或相等初始化
underscore_d

3
@rvighne,不,那是不正确的。constexpr表示constvar,而不表示类型。即static constexpr const char* const与相同static constexpr const char*,但不相同static constexpr char*
midenok '16

2
@ abyss.7-感谢您的回答,我还有一个请回答:为什么它必须是静态的?
Guy Avraham

34

在类定义内部,您只能声明静态成员。它们必须在类之外定义。对于编译时积分常量,该标准例外,您可以“初始化”成员。但是,这仍然不是一个定义。例如,没有定义就无法使用该地址。

我想提及的是,我没有看到使用std :: string而不是const char [] 作为常量的好处。std :: string很不错,除了它以外,它都需要动态初始化。所以,如果你写类似

const std::string foo = "hello";

在名称空间范围内,foo的构造函数将在执行main开始之前立即运行,并且此构造函数将在堆内存中创建常量“ hello”的副本。除非您真的需要RECTANGLE成为std :: string,否则您也可以编写

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

那里!没有堆分配,没有复制,没有动态初始化。

干杯


1
这是C ++ 11之前的答案。使用标准C ++并使用std :: string_view。

1
C ++ 11没有std :: string_view。
卢卡斯·萨利奇

17

这只是额外的信息,但是如果您确实希望将字符串包含在头文件中,请尝试以下操作:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

虽然我怀疑这是值得推荐的。


看起来很酷:)-我猜您除了C ++以外还拥有其他语言的背景吗?
磅。

5
我不会推荐它。我经常这样做。它工作正常,我发现比将字符串放入实现文件中更明显。尽管std :: string的实际数据仍然位于堆上。我将返回const char *,在这种情况下,您无需声明静态变量,因此声明将占用较少的空间(代码明智)。虽然只是口味的问题。
Zoomulator 2012年

14

在C ++ 17中,您可以使用内联变量

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

请注意,这与abyss.7的答案不同:这是一个实际的std::string对象,而不是一个const char*


您不认为使用inline会创建很多重复项吗?
舒瓦

1
@shuva否,该变量将不会重复
zett42

8

若要使用该类内初始化语法,该常数必须是通过常数表达式初始化的整数或枚举类型的静态const。

这是限制。因此,在这种情况下,您需要在类外部定义变量。引用@AndreyT的答案


7

类静态变量可以在标头中声明,但必须在.cpp文件中定义。这是因为静态变量只能有一个实例,并且编译器无法决定将其放置在哪个生成的目标文件中,因此您必须做出决定。

为了使用C ++ 11中的声明保留静态值的定义,可以使用嵌套的静态结构。在这种情况下,静态成员是结构,必须在.cpp文件中定义,但值在标头中。

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

无需初始化单个成员,而是在.cpp中初始化整个静态结构:

A::_Shapes A::shape;

通过以下方式访问值

A::shape.RECTANGLE;

或-由于成员是私有成员,并且只能从A使用-

shape.RECTANGLE;

注意,该解决方案仍然遭受静态变量的初始化顺序的问题。当使用静态值初始化另一个静态变量时,第一个静态变量可能尚未初始化。

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

在这种情况下,静态变量将包含{“”}或{“ .h”,“ .hpp”},具体取决于链接程序创建的初始化顺序。

如@ abyss.7所述,constexpr如果可以在编译时计算变量的值,则也可以使用。但是,如果用声明字符串,static constexpr const char*并且程序使用了std::string否则,则会产生开销,因为std::string每次使用该常量时都会创建一个新对象:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}

准备充分的答案Marko。有两个细节:一个不需要静态类成员的cpp文件,还请对任何类型的常量使用std :: string_view。


4

可能就做:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

要么

#define RECTANGLE "rectangle"

11
当可以使用类型常量时使用#define是错误的。
阿图尔·扎伊卡

如果没有,第一个示例基本上是一个很好的解决方案,constexpr但不能创建静态函数const
Frank Puffer

应该避免这种解决方案。每次调用都会创建一个新字符串。这样会更好:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon

为什么使用成熟的容器作为返回值?使用std :: string_vew ..在这种情况下,其内容将保持有效。甚至更好地使用字符串文字来生成并返回字符串视图...最后但并非最不重要的一点,const返回值在这里没有任何意义或影响.ah是的,并且在某些标头中将其作为内联而不是静态的命名空间...并且请使其成为constexpr

4

您可以采用上述const char*解决方案,但是如果您始终需要使用字符串,则将产生大量开销。
另一方面,静态字符串需要动态初始化,因此,如果要在另一个全局/静态变量的初始化期间使用其值,则可能会遇到初始化顺序的问题。为避免这种情况,最便宜的方法是通过getter访问静态字符串对象,该方法检查对象是否已初始化。

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

记住只能使用A::getS()。由于任何线程只能以开头main(),并且只能A_s_initialized在之前初始化main(),因此即使在多线程环境中也不需要锁。A_s_initialized默认情况下为0(在动态初始化之前),因此如果getS()在s初始化之前使用,则可以安全地调用init函数。

顺便说一句,在上面的答案中:“ static const std :: string RECTANGLE()const ”,静态函数不能const因为它们无论如何都不能改变状态(没有此指针)。


4

快进到2018和C ++ 17。

  • 不要使用std :: string,使用std :: string_view文字
  • 请注意“ constexpr”波纹管。这也是一种“编译时”机制。
  • 没有内联并不意味着重复
  • 不需要cpp文件
  • static_assert仅在编译时有效

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

以上是适当的法律标准的C ++公民。它可以很容易地参与任何和所有std ::算法,容器,实用程序等。例如:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

享受标准的C ++


使用std::string_view仅当您使用常量string_view在所有的函数的参数。如果任何函数使用const std::string&参数,则当您string_view通过该参数传递常量时,将创建字符串的副本。如果您的常数是类型std::string,则既不会为const std::string&参数也不会为std::string_view参数创建副本。
MarkoMahnič19年

很好的答案,但对为什么从函数返回string_view感到好奇?在inline变量以其ODR语义到达C ++ 17 之前,这种技巧非常有用。但是string_view也是C ++ 17,所以constexpr auto some_str = "compile time"sv;工作也就完成了(实际上,它不是变量,而是constexpr,所以inline是隐式的;如果您有一个变量,即没有constexpr,那么inline auto some_str = "compile time"sv;就可以了,尽管当然有一个命名空间范围变量,它本质上是一个全局变量,很少是一个好主意)。
失去心理
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.