簡體   English   中英

如何在 C++ 中初始化私有靜態成員?

[英]How to initialize private static members in C++?

在 C++ 中初始化私有靜態數據成員的最佳方法是什么? 我在頭文件中嘗試過這個,但它給了我奇怪的鏈接器錯誤:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

我猜這是因為我無法從類外部初始化私有成員。 那么最好的方法是什么?

類聲明應在頭文件中(如果未共享,則在源文件中)。
文件:foo.h

class foo
{
    private:
        static int i;
};

但是初始化應該在源文件中。
文件:foo.cpp

int foo::i = 0;

如果初始化在頭文件中,則包含頭文件的每個文件都將具有靜態成員的定義。 因此,在鏈接階段,您將收到鏈接器錯誤,因為初始化變量的代碼將在多個源文件中定義。 static int i的初始化必須在任何函數之外完成。

注意: Matt Curtis:指出如果靜態成員變量是 const int 類型(例如intboolchar ),C++ 允許簡化上述內容。 然后可以直接在頭文件的類聲明中聲明和初始化成員變量:

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

對於變量

foo.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;
};

從 C++17 開始,可以使用inline關鍵字在標頭中定義靜態成員。

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

“可以內聯聲明靜態數據成員。可以在類定義中定義內聯靜態數據成員,並且可以指定默認成員初始值設定項。它不需要類外定義:”

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

對於這個問題的未來觀眾,我想指出你應該避免monkey0506所暗示的

頭文件用於聲明。

頭文件為直接或間接#includes每個.cpp文件編譯一次,並且任何函數之外的代碼在程序初始化時運行,在main()之前。

通過放置: foo::i = VALUE; 在頭文件中, foo:i將為每個.cpp文件分配值VALUE (無論是什么),並且這些分配將在main()運行之前以不確定的順序(由鏈接器確定)發生。

如果我們在其中一個.cpp文件中#define VALUE為不同的數字會怎樣? 它會編譯得很好,在我們運行程序之前,我們無法知道哪一個獲勝。

永遠不要將執行的代碼放入標題中,原因與您永遠不會#include .cpp文件的原因相同。

包含守衛(我同意你應該總是使用它)保護你免受不同的東西:在編譯單個.cpp文件時,同一個標頭被間接#include d 多次

使用 Microsoft 編譯器 [1],也可以在頭文件中定義非int靜態變量,但在類聲明之外,使用 Microsoft 特定的__declspec(selectany)

class A
{
    static B b;
}

__declspec(selectany) A::b;

請注意,我並不是說這很好,我只是說它可以做到。

[1] 現在,支持__declspec(selectany)編譯器比 MSC 多——至少是 gcc 和 clang。 也許更多。

int foo::i = 0; 

是初始化變量的正確語法,但它必須在源文件 (.cpp) 中而不是在頭文件中。

因為它是一個靜態變量,編譯器只需要創建它的一個副本。 您必須在代碼中的某處使用一行“int foo:i”來告訴編譯器將它放在哪里,否則會出現鏈接錯誤。 如果它在標題中,您將在包含標題的每個文件中獲得一個副本,因此從鏈接器獲得多重定義的符號錯誤。

如果你想初始化一些復合類型(fe string),你可以這樣做:

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變量initialize _list為您需要的值。 getList任何后續調用getList將簡單地返回已初始化的_list對象。

當然,您必須始終通過調用getList()方法來訪問_list對象。

適用於多個對象的 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 上游.

編譯並運行:

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

另請參閱: C++ 中的靜態構造函數? 我需要初始化私有靜態對象

在 Ubuntu 19.04 上測試。

C++17 內聯變量

提到: https : //stackoverflow.com/a/45062055/895245但這里有一個多文件可運行示例,使其更加清晰: 內聯變量如何工作?

我在這里沒有足夠的代表將其添加為評論,但 IMO 無論如何用#include 保護編寫標題是一種很好的風格,正如 Paranaix 幾個小時前指出的那樣,這將防止多定義錯誤。 除非您已經在使用單獨的 CPP 文件,否則沒有必要僅使用一個來初始化靜態非整數成員。

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

我認為沒有必要為此使用單獨的 CPP 文件。 當然,你可以,但沒有技術上的理由為什么你必須這樣做。

如果您使用頭文件保護,您還可以在頭文件中包含分配。 我已將此技術用於我創建的 C++ 庫。 實現相同結果的另一種方法是使用靜態方法。 例如...

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

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

上面的代碼具有不需要 CPP/源文件的“好處”。 同樣,我用於我的 C++ 庫的方法。

我遵循卡爾的想法。 我喜歡它,現在我也使用它。 我改變了一點符號並添加了一些功能

#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

您遇到的鏈接器問題可能是由以下原因引起的:

  • 在頭文件中提供類和靜態成員定義,
  • 在兩個或多個源文件中包含此標頭。

對於那些從 C++ 開始的人來說,這是一個常見的問題。 靜態類成員必須在單個翻譯單元中初始化,即在單個源文件中。

不幸的是,靜態類成員必須在類主體之外初始化。 這使編寫僅標頭代碼變得復雜,因此,我使用了完全不同的方法。 您可以通過靜態或非靜態類函數提供靜態對象,例如:

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

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

也在 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

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變量就會被初始化。

這不會與其他評論不一致,實際上它遵循在全局范圍內初始化變量的相同原則,但是通過使用這種方法,我們使其明確(並且易於理解)而不是定義掛在那里的變量。

定義常量的一種“老派”方法是將它們替換為enum

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

這種方式不需要提供定義,並且避免了生成常量lvalue ,這可以為您省去一些麻煩,例如當您不小心ODR 使用它時。

以下是一個簡單示例中的所有可能性和錯誤......

#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

當我第一次遇到這個時,我只是想提一些對我來說有點奇怪的東西。

我需要在模板類中初始化私有靜態數據成員。

在 .h 或 .hpp 中,初始化模板類的靜態數據成員看起來像這樣:

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

這是否符合您的目的?

//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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM