在C ++类中初始化静态变量?


78

我注意到,我在类中的某些函数实际上并未访问该对象,因此我将它们制成了static。然后编译器告诉我,它们访问的所有变量也必须是静态的-到目前为止,还可以理解。我有一堆字符串变量,例如

string RE_ANY = "([^\\n]*)";
string RE_ANY_RELUCTANT = "([^\\n]*?)";

等等。然后,我将它们全部制成,static const因为它们永不改变。但是,我的程序只有在将它们移出类时才进行编译:否则,MSVC ++ 2010会抱怨“只能在类中初始化静态常数积分变量”。

好吧,这很不幸。有解决方法吗?我想把它们留在他们所属的类中。

Answers:


127

它们不能在类内部初始化,但是可以在类外部在源文件中初始化:

// inside the class
class Thing {
    static string RE_ANY;
    static string RE_ANY_RELUCTANT;
};

// in the source file
string Thing::RE_ANY = "([^\\n]*)";
string Thing::RE_ANY_RELUCTANT = "([^\\n]*?)";

更新资料

我只注意到你的问题的第一线-你希望使这些功能static,你想他们const。使它们static成为对象意味着它们不再与对象关联(因此它们不能访问任何非静态成员),使数据成为静态对象意味着它将与该类型的所有对象共享。这可能不是您想要的。设置它们const只是意味着它们不能修改任何成员,但仍然可以访问它们。


1
它们不访问对象中的任何内容,只是访问作为参考参数提供给它们的临时变量。因此,它们可能应该同时为conststatic
菲利克斯·丹贝克

8
@Felix:这是不可能的,这const意味着它不会修改this……并且没有thisforstatic方法。
Matthieu M.

2
@ androidplusios.design:我不关注。您需要使用string它,因为它是一个变量定义,需要一个类型说明符,并且类型为string()如果愿意,可以放置一个声明符,它不会改变含义。
Mike Seymour 2015年

4
@ androidplusios.design:它确实看到了。但是您必须使用定义语法来定义和初始化它,并且包括一个类型说明符,无论之前是否已声明它。这就是指定语言的方式。
Mike Seymour 2015年

4
@ androidplusios.design:或者,如果您问的是语法为何如此,并带有所有的不一致,冗余和歧义:因为它是几十年来从更简单的语言演变而来的。
Mike Seymour

32

迈克·西摩(Mike Seymour)为您提供了正确的答案,但要补充一点……正如编译器所告诉的那样,
C ++使您只能在类主体中声明和定义静态const整数类型。因此,您实际上可以执行以下操作:

class Foo
{
    static const int someInt = 1;
    static const short someShort = 2;
    // etc.
};

而且您不能使用任何其他类型来执行此操作,在这种情况下,应在.cpp文件中定义它们。


16

静态成员变量必须在类中声明,然后在其外部定义!

没有解决方法,只需将其实际定义放在源文件中即可。


从您的描述中,闻起来好像您没有以正确的方式使用静态变量。如果它们从不改变,则应改用常量变量,但是您的描述太笼统了,不能多说。

静态成员变量对于类的任何实例始终具有相同的值:如果您更改一个对象的静态变量,则所有其他对象的静态变量也会改变(实际上,您也可以在没有类实例的情况下访问它们-即:一个对象)。


3
现在它们是const的-它们也只需要是静态的,这样我就可以在静态成员函数中使用它们。该规则必须在类内部声明并在类外部定义的原因是什么?这对我来说没有多大意义。
菲利克斯·唐贝克

2
@Felix Dombek:我认为原因是为您编译和链接的每个源文件都声明了(/可以声明)该类,但是实际变量必须仅定义一次。这就是需要将其显式声明为extern其他源文件中定义的变量的相同原因。
peoro 2011年

1
@peoro:看来合理!但是,为什么允许整数数据类型呢?那也不应该被允许,然后...
Felix Dombek

1
@Felix Dombek:整数类型也不允许。如果您定义此struct:,struct A { static int x; };并且尝试在A::xint A::x;在源文件中定义的情况下进行访问,则代码将不会链接。
peoro 2011年

2
@Felix Dombek:这不是标准的投诉。ISO C ++禁止非常量静态成员的类内初始化。您只能对整型const静态成员执行此操作,这是因为静态const整型变量实际上不会存储在内存中,而是在编译时用作常量。
peoro 2011年

16

从C ++ 11开始,可以使用来在类内部完成此操作constexpr

class stat {
    public:
        // init inside class
        static constexpr double inlineStaticVar = 22;
};

现在可以通过以下方式访问该变量:

stat::inlineStaticVar

1
绝对适合int,double,pointer等。但是,这对于问题中的字符串不起作用,因为string不是文字或引用。
罗伊·丹顿

1
好点子。不过,您可以做到constexpr char RE_ANY[] = "([^\\n]*)";constexpr std::string_view RE_ANY("([^\\n]*)", 9);
0ax1'4

9

我觉得值得补充的是,静态变量与常量变量并不相同。

在类中使用常量

struct Foo{
    const int a;
    Foo(int b) : a(b){}
}

我们会像这样声明它

fooA = new Foo(5);
fooB = new Foo(10);
// fooA.a = 5;
// fooB.a = 10;

对于静态变量

struct Bar{
    static int a;
    Foo(int b){
        a = b;
    }
}
Bar::a = 0; // set value for a

像这样使用

barA = new Bar(5);
barB = new Bar(10);
// barA.a = 10;
// barB.a = 10;
// Bar::a = 10;

你看这里发生了什么。与Foo的每个实例一起实例化的常量变量,因为Foo的实例化对于Foo的每个实例具有单独的值,并且Foo根本不能更改它。

与Bar一样,它们仅是Bar :: a的一个值,无论制作了多少Bar实例。它们都共享此值,您也可以将其作为Bar的任何实例进行访问。静态变量还遵守public / private的规则,因此您可以使只有Bar的实例才能读取Bar :: a;的值。


1
嗯...发现它很有趣没有人指出我可怕的不必要使用“新”。对这个问题并不是很在意,但是,是的,避免在不需要时使用“ new”,或者您或多或少都不会使用。
thecoshman 2014年

请删除您对的可怕,不必要的使用new。;-)
Roi Danton

1
我认为它现在已成为旧代码有多糟糕的历史性例子。在这一点上,我认为将其删除是无益的。
thecoshman '18 -10-1

8

只是在其他答案之上。为了初始化复杂的静态成员,您可以按照以下步骤进行操作:

照常声明静态成员。

// myClass.h
class myClass
{
static complexClass s_complex;
//...
};

如果不是很简单,请编写一个小函数来初始化您的类。这将仅在静态成员初始化时被调用。(请注意,将使用complexClass的副本构造函数,因此应明确定义)。

//class.cpp    
#include myClass.h
complexClass initFunction()
{
    complexClass c;
    c.add(...);
    c.compute(...);
    c.sort(...);
    // Etc.
    return c;
}

complexClass myClass::s_complex = initFunction();

5

一些答案似乎有点误导

你不必...

  • 分配一个值初始化的时候,因为赋值是一些静态的对象可选
  • 创建另一个.cpp文件进行初始化,因为它可以在同一Header文件中完成。

同样,您甚至可以使用inline关键字在同一类范围内初始化静态对象,就像普通变量一样。


在同一文件中没有任何值初始化

#include <string>
class A
{
    static std::string str;
    static int x;
};
std::string A::str;
int A::x;

用同一文件中的值初始化

#include <string>
class A
{
    static std::string str;
    static int x;
};
std::string A::str = "SO!";
int A::x = 900;

使用inline关键字在同一类范围内初始化

#include <string>
class A
{
    static inline std::string str = "SO!";
    static inline int x = 900;
};

2
最好的答案。我添加了inline,所有链接错误都消失了。
康坦戈

“内联变量是c ++ 17扩展”。这就是我得到的信息
Ssenyonjo,

@Ssenyonjo您必须将C ++语言标准设置为C ++ 17或更高版本。即在Visual Studio中,转到项目->属性-> C / C ++->语言-> C ++语言标准-> ISO C ++ 17或ISO C ++ Latest。而在GCC,使用标志-std=c++17-std=c++20
Beyondo

2

如果您的目标是初始化标头文件(而不是* .cpp文件,如果您坚持使用“仅标头”的习惯用法,则可能要使用该标头)中的静态变量,则可以通过使用模板。模板化的静态变量可以在标头中初始化,而不会导致定义多个符号。

请参阅此处的示例:

类模板中的静态成员初始化


1

(可选)将所有常量移动到.cpp文件,而无需在.h文件中声明。使用匿名名称空间使它们在cpp模块之外不可见。

// MyClass.cpp

#include "MyClass.h"

// anonymous namespace
namespace
{
    string RE_ANY = "([^\\n]*)";
    string RE_ANY_RELUCTANT = "([^\\n]*?)";
}

// member function (static or not)
bool MyClass::foo()
{
    // logic that uses constants
    return RE_ANY_RELUCTANT.size() > 0;
}
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.