ChatGPT解决这个技术问题 Extra ChatGPT

如何在 C++ 中初始化私有静态成员?

在 C++ 中初始化私有静态数据成员的最佳方法是什么?我在我的头文件中尝试了这个,但它给了我奇怪的链接器错误:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

我猜这是因为我无法从类外初始化私有成员。那么最好的方法是什么?

嗨杰森。我没有找到关于静态成员(尤其是积分成员)的默认初始化的评论。事实上你需要写 int foo::i 以便链接器可以找到它,但它会自动初始化为 0!这一行就足够了: int foo::i; (这对存储在静态内存中的所有对象都有效,链接器负责初始化静态对象。)
以下答案不适用于模板类。他们说:初始化必须进入源文件。对于模板类,这既不可能,也没有必要。
C++17 允许静态数据成员的内联初始化(即使对于非整数类型):inline static int x[] = {1, 2, 3};。请参阅en.cppreference.com/w/cpp/language/static#Static_data_members

r
rawrex

类声明应该在头文件中(如果不共享,则在源文件中)。文件:foo.h

class foo
{
    private:
        static int i;
};

但是初始化应该在源文件中。文件:foo.cpp

int foo::i = 0;

如果初始化在头文件中,则包含头文件的每个文件都将具有静态成员的定义。因此,在链接阶段,您将收到链接器错误,因为初始化变量的代码将在多个源文件中定义。 static int i 的初始化必须在任何函数之外完成。

注意: Matt Curtis:指出如果静态成员变量是 const integer type (bool, char, char8_t [因为 C ++20]、char16_tchar32_twchar_tshortintlonglong long 或任何实现定义的扩展整数类型,包括任何有符号、无符号和 cv -合格的变体。)。然后,您可以直接在头文件的类声明中声明和初始化成员变量:

class foo
{
    private:
        static int const i = 42;
};

是的。但我假设问题已被简化。从技术上讲,声明和定义都可以在一个源文件中。但这限制了其他类对类的使用。
实际上不仅仅是 POD,它也必须是 int 类型(int、short、bool、char ...)
请注意,这不仅仅是值如何初始化的问题:像这样定义的 const 整数类型可能会被实现转换为编译时常量。这并不总是您想要的,因为它会增加二进制依赖性:如果值更改,客户端代码需要重新编译。
@Martin:除了更正s/POD/integral type/之外,如果曾经使用过地址,那么还需要定义。听起来很奇怪,在类定义中带有初始化器的声明不是定义。模板化的 const 习惯用法为您需要在头文件中定义的情况提供了一种解决方法。另一个更简单的解决方法是生成局部静态常量值的函数。干杯&hth.,
您可以添加说明 int foo::i =0;不应该在函数内部(包括主函数)。我在我的主要功能开始时有它,但它不喜欢那样。
M
Matt Curtis

对于变量:

富.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

这是因为您的程序中只能有一个 foo::i 实例。它相当于头文件中的 extern int i 和源文件中的 int i

对于常量,您可以将值直接放在类声明中:

class foo
{
private:
    static int i;
    const static int a = 42;
};

这是一个有效的观点。我也会添加这个我的解释。但应该注意的是,这只适用于 POD 类型。
从什么时候开始,C++ 允许在类内声明并且没有整数类型的定义。由于 C++98 本身或 C++03 或何时?请分享真实链接。 C++ 标准措辞与编译器不同步。他们提到如果使用该成员,则仍应定义该成员。所以,我不需要 C++ 标准引用
我想知道为什么 private 变量可以在 Class 之外在这里初始化,这也可以用于非静态变量。
你找到解释了吗? @Krishna_Oza
@nn0p 还没有,但是 Class 之外的非静态私有变量初始化在 Cpp 中没有任何意义。
X
Xunie

从 C++17 开始,静态成员可以使用 inline 关键字在标头中定义。

http://en.cppreference.com/w/cpp/language/static

“静态数据成员可以内联声明。内联静态数据成员可以在类定义中定义,并且可以指定默认成员初始化程序。它不需要类外定义:”

struct X
{
    inline static int n = 1;
};

从 C++17 开始,这是可能的,它目前正在成为新标准。
s
sifferman

对于这个问题的未来观众,我想指出您应该避免monkey0506 is suggesting

头文件用于声明。

对于直接或间接#includes它们的每个 .cpp 文件,头文件都会编译一次,并且任何函数之外的代码在程序初始化时运行,main() 之前。

通过将: foo::i = VALUE; 放入标题中,将为每个 .cpp 文件分配 VALUE 值(无论是什么),并且这些分配将在 { 之前以不确定的顺序(由链接器确定)发生5}运行。

如果我们的 .cpp 文件之一中的 #define VALUE 是不同的数字怎么办?它将编译得很好,在我们运行程序之前,我们无法知道哪个获胜。

永远不要将执行的代码放入标头中,原因与您从不#include .cpp 文件的原因相同。

包含防护(我同意您应该始终使用)保护您免受不同的影响:在编译单个 .cpp 文件时,同一个标头被间接 #included 多次。


你当然是对的,除了类模板(没有被问到,但我碰巧处理了很多)。因此,如果类是完全定义的而不是类模板,则将这些静态成员放在单独的 CPP 文件中,但对于类模板,定义必须在同一个翻译单元中(例如,头文件)。
@monkey_05_06:这似乎只是避免模板代码中的静态成员的一个论点:您已经为每个类的实例化了一个静态成员。可能将标头编译成多个 cpp 文件会使问题变得更糟……您可能会得到大量相互冲突的定义。
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… 此链接描述了在 main 函数中实例化静态模板成员,如果有点负担的话,它会更简洁。
你的论点真的很牵强。首先,您不能#define VALUE,因为宏名称不是有效标识符。即使你可以——谁会那样做?头文件用于声明 - ?来吧.. 唯一应该避免将值放在标题中的情况是与 odr-used 作斗争。并且每当您需要更改值时,将值放在标头中可能会导致不必要的重新编译。
J
Johann Gerell

使用 Microsoft 编译器 [1],与 int 不同的静态变量也可以在头文件中定义,但在类声明之外,使用 Microsoft 特定的 __declspec(selectany)

class A
{
    static B b;
}

__declspec(selectany) A::b;

请注意,我并不是说这很好,我只是说可以做到。

[1] 如今,支持 __declspec(selectany) 的编译器比 MSC 多 - 至少 gcc 和 clang。也许更多。


D
David Dibben
int foo::i = 0; 

是初始化变量的正确语法,但它必须放在源文件 (.cpp) 中而不是头文件中。

因为它是一个静态变量,编译器只需要创建它的一个副本。您必须在代码中的某个位置有一行“int foo:i”来告诉编译器将它放在哪里,否则会出现链接错误。如果它在标题中,您将在每个包含标题的文件中获得一个副本,因此从链接器获取多重定义的符号错误。


Z
Ziezi

如果你想初始化一些复合类型(fe 字符串),你可以这样做:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

由于 ListInitializationGuardSomeClass::getList() 方法中的静态变量,因此它只会被构造一次,这意味着构造函数被调用一次。这将 initialize _list 变量设置为您需要的值。对 getList 的任何后续调用都将简单地返回已初始化的 _list 对象。

当然,您必须始终通过调用 getList() 方法来访问 _list 对象。


下面是这个习惯用法的一个版本,它不需要为每个成员对象创建一个方法:stackoverflow.com/a/48337288/895245
C
Ciro Santilli Путлер Капут 六四事

适用于多个对象的 C++11 静态构造函数模式

https://stackoverflow.com/a/27088552/895245 提出了一个习惯用法,但这里有一个更简洁的版本,不需要为每个成员创建新方法。

主文件

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub upstream

编译并运行:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

另请参阅:static constructors in C++? I need to initialize private static objects

在 Ubuntu 19.04 上测试。

C++17 内联变量

在:https://stackoverflow.com/a/45062055/895245 中提到,但这里有一个多文件可运行示例以使其更加清晰:How do inline variables work?

这个很棒的 C++17 特性使我们能够:

方便地为每个常量只使用一个内存地址

将其存储为 constexpr:如何声明 constexpr extern?

从一个标题在一行中执行

主文件

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

不是main.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

非main.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream


m
monkey0506

我没有足够的代表在这里将其添加为评论,但是 IMO 无论如何用 #include guards 编写标题是一种很好的风格,正如 Paranaix 几小时前所指出的那样,这可以防止多定义错误。除非您已经在使用单独的 CPP 文件,否则没有必要仅使用一个来初始化静态非整数成员。

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

我认为不需要为此使用单独的 CPP 文件。当然,你可以,但没有技术上的理由你必须这样做。


#include 守卫只是防止每个翻译单元有多个定义。
关于良好的风格:您应该在结束 endif 上添加评论:#endif // FOO_H
这仅在您只有一个包含 foo.h 的编译单元时才有效。如果两个或多个 cpp 包含 foo.h,这是一种典型情况,每个 cpp 将声明相同的静态变量,因此链接器将抱怨 `foo::i' 的多个定义,除非您对文件使用包编译(编译只有一个包含所有 cpps 的文件)。但是,尽管包编译很棒,但问题的解决方案是在 cpp 中声明 (int foo::i = 0;)!
或者只使用 #pragma once
#pragma once 是通过翻译单元对多个定义的解决方案,但它应该是最终的、不得已的解决方案,而不是一个人的编码风格......
佚名

如果您使用标头保护,您还可以在头文件中包含分配。我已将这种技术用于我创建的 C++ 库。实现相同结果的另一种方法是使用静态方法。例如...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

上面的代码具有不需要 CPP/源文件的“好处”。同样,我用于 C++ 库的方法。


A
Alejadro Xalabarder

我遵循卡尔的想法。我喜欢它,现在我也使用它。我改变了一点符号并添加了一些功能

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

这输出

mystatic value 7
mystatic value 3
is my static 1 0

n
no one special

您遇到的链接器问题可能是由以下原因引起的:

在头文件中提供类和静态成员定义,

在两个或多个源文件中包含此标头。

对于那些从 C++ 开始的人来说,这是一个常见的问题。静态类成员必须在单个翻译单元中初始化,即在单个源文件中。

不幸的是,静态类成员必须在类主体之外进行初始化。这使编写仅标头代码变得复杂,因此,我使用了完全不同的方法。您可以通过静态或非静态类函数提供静态对象,例如:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};

就 C++ 而言,我仍然是一个完整的 n00b,但这对我来说看起来很棒,非常感谢!我免费获得了单例对象的完美生命周期管理。
a
andrew

也在 privateStatic.cpp 文件中工作:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic

Z
Ziezi

set_default() 方法呢?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

我们只需要使用 set_default(int x) 方法,我们的 static 变量就会被初始化。

这不会与其他评论意见相左,实际上它遵循在全局范围内初始化变量的相同原则,但是通过使用这种方法,我们使其明确(并且易于理解)而不是定义的变量挂在那里。


a
anatolyg

定义常量的一种“老派”方式是用 enum 替换它们:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

这种方式不需要提供定义,并且避免了常量 lvalue,这可以为您省去一些麻烦,例如当您不小心 ODR-use 它时。


c
cat

以下是一个简单示例中的所有可能性和错误...

#ifndef Foo_h
#define Foo_h

class Foo
{
  static const int a = 42; // OK
  static const int b {7};  // OK
  //static int x = 42; // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
  //static int y {7};  // ISO C++ forbids in-class initialization of non-const static member 'Foo::x'
  static int x;
  static int y;
  int m = 42;
  int n {7};
};

// Foo::x = 42;  // error: 'int Foo::x' is private
int Foo::x = 42; // OK in Foo.h if included in only one  *.cpp -> *.o file!
int Foo::y {7};  // OK

// int Foo::y {7};  // error: redefinition of 'int Foo::y'
   // ONLY if the compiler can see both declarations at the same time it, 
   // OTHERWISE you get a linker error

#endif // Foo_h

但最好把它放在 Foo.cpp 中。这样您可以单独编译每个文件并稍后链接它们,否则 Foo:x 将出现在多个目标文件中并导致链接器错误。 ...

// Foo::x = 42;  // error: 'int Foo::x' is private, bad if Foo::X is public!
int Foo::x = 42; // OK in Foo.h if included in only one  *.cpp -> *.o file!
int Foo::y {7};  // OK

T
Tyler Heers

当我第一次遇到这个时,我只是想提一些对我来说有点奇怪的事情。

我需要在模板类中初始化一个私有静态数据成员。

在 .h 或 .hpp 中,初始化模板类的静态数据成员看起来像这样:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

c
corporateAbaper

这是否符合您的目的?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}