Bagaimana cara menginisialisasi anggota statis pribadi di C ++?

520

Apa cara terbaik untuk menginisialisasi anggota data pribadi yang statis di C ++? Saya mencoba ini di file header saya, tetapi itu memberi saya kesalahan linker aneh:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Saya kira ini karena saya tidak dapat menginisialisasi anggota pribadi dari luar kelas. Jadi apa cara terbaik untuk melakukan ini?

Jason Baker
sumber
2
Hai Jason. Saya tidak menemukan komentar tentang inisialisasi default anggota statis (terutama yang integral). Sebenarnya Anda perlu menulis int foo :: i agar linker dapat menemukannya, tetapi akan diinisialisasi secara otomatis dengan 0! Baris ini sudah cukup: int foo :: i; (Ini berlaku untuk semua objek yang disimpan dalam memori statis, linker bertugas menginisialisasi objek statis.)
Nico
1
Jawaban di bawah ini tidak berlaku untuk kelas templat. Mereka mengatakan: inisialisasi harus masuk ke file sumber. Untuk kelas templat, ini tidak mungkin, atau perlu.
Joachim W
7
C ++ 17 memungkinkan inisialisasi inline anggota data statis (bahkan untuk jenis non-integer): inline static int x[] = {1, 2, 3};. Lihat en.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Jawaban:

557

Deklarasi kelas harus di file header (Atau di file sumber jika tidak dibagikan).
File: foo.h

class foo
{
    private:
        static int i;
};

Tetapi inisialisasi harus dalam file sumber.
File: foo.cpp

int foo::i = 0;

Jika inisialisasi ada dalam file header maka setiap file yang menyertakan file header akan memiliki definisi anggota statis. Jadi selama fase tautan Anda akan mendapatkan kesalahan tautan karena kode untuk menginisialisasi variabel akan didefinisikan dalam beberapa file sumber. Inisialisasi static int iharus dilakukan di luar fungsi apa pun.

Catatan: Matt Curtis: poin bahwa C ++ memungkinkan penyederhanaan di atas jika statis variabel anggota adalah const tipe int (misalnya int, bool, char). Anda kemudian dapat mendeklarasikan dan menginisialisasi variabel anggota langsung di dalam deklarasi kelas di file header:

class foo
{
    private:
        static int const i = 42;
};
Martin York
sumber
4
Iya. Tetapi saya berasumsi bahwa pertanyaannya telah disederhanakan. Secara teknis deklarasi dan definisi semua bisa dalam satu file sumber tunggal. Tapi itu kemudian membatasi penggunaan kelas oleh kelas lain.
Martin York
11
sebenarnya bukan hanya POD, itu harus menjadi tipe int juga (int, pendek, bool, char ...)
Matt Curtis
9
Perhatikan bahwa ini bukan hanya pertanyaan tentang bagaimana nilai diinisialisasi: tipe integral const didefinisikan seperti ini dapat diubah menjadi konstanta waktu kompilasi oleh implementasi. Ini tidak selalu seperti yang Anda inginkan, karena ini meningkatkan ketergantungan biner: kode klien perlu dikompilasi ulang jika nilainya berubah.
Steve Jessop
5
@ Martin: selain koreksi s / POD / tipe integral /, jika alamat pernah diambil maka perlu ada definisi juga. Meski terdengar aneh, deklarasi dengan penginisialisasi, dalam definisi kelas, bukan definisi. The idiom const templated menyediakan solusi untuk kasus-kasus di mana Anda perlu definisi dalam file header. Solusi lain dan lebih sederhana adalah fungsi yang menghasilkan nilai konstanta statis lokal. Ceria & hth.,
Ceria dan hth. - Alf
3
Anda dapat menambahkan klarifikasi bahwa int foo :: i = 0; seharusnya tidak berada di dalam suatu fungsi (termasuk fungsi utama). Saya memilikinya di awal fungsi utama saya dan tidak seperti itu.
qwerty9967
89

Untuk variabel :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Ini karena hanya ada satu contoh foo::idalam program Anda. Ini semacam setara extern int idalam file header dan int ifile sumber.

Untuk konstanta, Anda bisa meluruskan nilainya dalam deklarasi kelas:

class foo
{
private:
    static int i;
    const static int a = 42;
};
Matt Curtis
sumber
2
Ini adalah poin yang valid. Saya akan menambahkan ini juga penjelasan saya. Tetapi harus dicatat ini hanya berfungsi untuk tipe POD.
Martin York
Sejak saat itu, C ++ memungkinkan untuk menjadi baik dengan deklarasi di kelas dan tidak ada definisi untuk tipe integral. Karena C ++ 98 sendiri atau C ++ 03 atau kapan? Silakan bagikan tautan otentik. Kata-kata standar C ++ tidak sinkron dengan kompiler. Mereka menyebutkan anggota akan tetap ditentukan jika digunakan. Jadi, saya tidak perlu mengutip C ++ Standard
smRaj
1
Saya bertanya-tanya mengapa privatevariabel dapat diinisialisasi di luar Kelas di sini, dapatkah ini dilakukan untuk variabel non-statis juga.
Krishna Oza
Sudahkah Anda menemukan penjelasannya? @Krishna_Oza
nn0p
@ nn0p belum, tetapi inisialisasi variabel pribadi non-statis di luar Classtidak masuk akal dalam Cpp.
Krishna Oza
41

Sejak C ++ 17, anggota statis dapat didefinisikan di header dengan kata kunci inline .

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

"Anggota data statis dapat dinyatakan inline. Anggota data statis inline dapat didefinisikan dalam definisi kelas dan dapat menentukan penginisialisasi anggota default. Itu tidak memerlukan definisi di luar kelas:"

struct X
{
    inline static int n = 1;
};
Mati di Sente
sumber
1
Ini dimungkinkan karena C ++ 17, yang saat ini sedang dalam proses menjadi standar baru.
Grebu
31

Untuk pemirsa pertanyaan ini di masa depan, saya ingin menunjukkan bahwa Anda harus menghindari apa yang disarankan oleh monkey0506 .

File header adalah untuk deklarasi.

File header dikompilasi satu kali untuk setiap .cppfile yang secara langsung atau tidak langsung #includes, dan kode di luar fungsi apa pun dijalankan pada inisialisasi program, sebelumnya main().

Dengan memasukkan: foo::i = VALUE;ke dalam header, foo:iakan diberikan nilai VALUE(apa pun itu) untuk setiap .cppfile, dan penugasan ini akan terjadi dalam urutan yang tidak ditentukan (ditentukan oleh tautan) sebelum main()dijalankan.

Bagaimana jika kita #define VALUEmenjadi nomor yang berbeda di salah satu .cppfile kita ? Ini akan dikompilasi dengan baik dan kita tidak akan tahu mana yang menang sampai kita menjalankan program.

Jangan pernah memasukkan kode yang dieksekusi ke header karena alasan yang sama bahwa Anda tidak pernah #includememiliki .cppfile.

termasuk penjaga (yang saya setuju Anda harus selalu gunakan) melindungi Anda dari sesuatu yang berbeda: header yang sama secara tidak langsung #included beberapa kali saat mengkompilasi satu .cppfile

Joshua Clayton
sumber
2
Anda benar tentang hal ini tentu saja, kecuali dalam kasus templat kelas (yang tidak ditanyakan, tetapi saya sering berurusan dengan banyak hal). Jadi jika kelas didefinisikan sepenuhnya dan bukan templat kelas, maka letakkan anggota statis ini dalam file CPP yang terpisah, tetapi untuk templat kelas definisi harus berada dalam unit terjemahan yang sama (misalnya, file header).
monkey0506
@ monkey_05_06: Itu sepertinya hanya argumen untuk menghindari anggota statis dalam kode templated: Anda sudah berakhir dengan satu anggota statis untuk setiap instance kelas. masalahnya diperburuk dengan kemungkinan mengkompilasi header menjadi beberapa file cpp ... Anda bisa mendapatkan rakit definisi yang bertentangan.
Joshua Clayton
publib.boulder.ibm.com/infocenter/macxhelp/v6v81/... Tautan ini menggambarkan instantiating anggota templat statis ke dalam fungsi utama, yang lebih bersih, jika sedikit membebani.
Joshua Clayton
1
Argumen Anda sangat besar. Pertama, Anda tidak dapat # mendefinisikan VALUE karena nama makro tidak menjadi pengidentifikasi yang valid. Dan kalaupun Anda bisa - siapa yang akan melakukan itu? File header untuk deklarasi -? Ayo .. Satu-satunya kasus di mana Anda harus menghindari menempatkan nilai dalam header adalah untuk melawan odr-used. Dan menempatkan nilai di header dapat menyebabkan kompilasi ulang yang tidak perlu setiap kali Anda perlu mengubah nilai.
Aleksander Fular
20

Dengan kompiler Microsoft [1], variabel statis yang tidak intseperti juga dapat didefinisikan dalam file header, tetapi di luar deklarasi kelas, menggunakan spesifik Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Perhatikan bahwa saya tidak mengatakan ini bagus, saya hanya mengatakan itu bisa dilakukan.

[1] Saat ini, lebih banyak kompiler daripada dukungan MSC __declspec(selectany)- setidaknya gcc dan dentang. Mungkin bahkan lebih.

Johann Gerell
sumber
17
int foo::i = 0; 

Adalah sintaks yang benar untuk menginisialisasi variabel, tetapi harus masuk dalam file sumber (.cpp) daripada di header.

Karena itu adalah variabel statis, kompiler hanya perlu membuat satu salinannya. Anda harus memiliki baris "int foo: i" di suatu tempat dalam kode Anda untuk memberi tahu kompiler di mana harus meletakkannya jika tidak, Anda mendapatkan kesalahan tautan. Jika itu ada di header, Anda akan mendapatkan salinan di setiap file yang menyertakan header, jadi dapatkan kesalahan simbol berlipat ganda dari linker.

David Dibben
sumber
12

Saya tidak punya cukup perwakilan di sini untuk menambahkan ini sebagai komentar, tetapi IMO itu gaya yang baik untuk menulis header Anda dengan #include penjaga , yang seperti dicatat oleh Paranaix beberapa jam yang lalu akan mencegah kesalahan definisi ganda. Kecuali Anda sudah menggunakan file CPP yang terpisah, tidak perlu menggunakan hanya untuk menginisialisasi anggota non-integral statis.

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Saya melihat tidak perlu menggunakan file CPP terpisah untuk ini. Tentu, Anda bisa, tetapi tidak ada alasan teknis mengapa Anda harus melakukannya.

monkey0506
sumber
21
#sertakan penjaga hanya mencegah beberapa definisi per unit terjemahan.
Paul Fultz II
3
mengenai gaya yang baik: Anda harus menambahkan komentar pada akhir penutupan:#endif // FOO_H
Riga
9
Ini hanya berfungsi jika Anda hanya memiliki satu unit kompilasi yang menyertakan foo.h. Jika dua atau lebih cpps menyertakan foo.h, yang merupakan situasi tipikal, masing-masing cpp akan mendeklarasikan variabel statis yang sama sehingga penghubung akan mengeluh dengan beberapa definisi `foo :: i 'kecuali Anda menggunakan kompilasi paket dengan file-file (kompilasi). hanya satu file yang menyertakan semua cpps). Tetapi meskipun kompilasi paket bagus, solusi untuk masalah ini adalah dengan mendeklarasikan (int foo :: i = 0;) dalam cpp!
Alejadro Xalabarder
1
Atau cukup gunakan#pragma once
tambre
12

Jika Anda ingin menginisialisasi beberapa tipe majemuk (fe string) Anda dapat melakukan sesuatu seperti itu:

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

Karena ListInitializationGuardmerupakan variabel statis di dalam SomeClass::getList()metode itu akan dibangun hanya sekali, yang berarti bahwa konstruktor dipanggil sekali. Ini akan initialize _listvariabel ke nilai yang Anda butuhkan. Setiap panggilan selanjutnya getListhanya akan mengembalikan objek yang sudah diinisialisasi _list.

Tentu saja Anda harus mengakses _listobjek selalu dengan memanggil getList()metode.

Kris Kwiatkowski
sumber
1
Berikut ini adalah versi idiom ini yang tidak memerlukan menciptakan satu metode per objek anggota: stackoverflow.com/a/48337288/895245
Ciro Santilli郝海东冠状病六四事件法轮功
9

C ++ 11 pola konstruktor statis yang berfungsi untuk banyak objek

Satu idiom diusulkan di: https://stackoverflow.com/a/27088552/895245 tetapi inilah versi yang lebih bersih yang tidak perlu membuat metode baru per anggota.

main.cpp

#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 hulu .

Kompilasi dan jalankan:

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

Lihat juga: konstruktor statis di C ++? Saya perlu menginisialisasi objek statis pribadi

Diuji pada Ubuntu 19.04.

C ++ 17 variabel sebaris

Disebutkan di: https://stackoverflow.com/a/45062055/895245 tapi di sini ada contoh runnable multi-guna membuatnya lebih jelas: Bagaimana cara kerja variabel inline?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
5

Anda juga bisa memasukkan tugas dalam file header jika Anda menggunakan penjaga header. Saya telah menggunakan teknik ini untuk pustaka C ++ yang telah saya buat. Cara lain untuk mencapai hasil yang sama adalah dengan menggunakan metode statis. Sebagai contoh...

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

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

Kode di atas memiliki "bonus" karena tidak memerlukan file CPP / sumber. Sekali lagi, metode yang saya gunakan untuk pustaka C ++ saya.


sumber
4

Saya mengikuti ide dari Karl. Saya menyukainya dan sekarang saya menggunakannya juga. Saya telah mengubah sedikit notasi dan menambahkan beberapa fungsi

#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));
}

output ini

mystatic value 7
mystatic value 3
is my static 1 0
Alejadro Xalabarder
sumber
3

Juga bekerja di file 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
andrew
sumber
3

Bagaimana dengan set_default()metode?

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

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

Kami hanya perlu menggunakan set_default(int x)metode dan kamistatic variabel akan diinisialisasi.

Ini tidak akan bertentangan dengan sisa komentar, sebenarnya mengikuti prinsip yang sama menginisialisasi variabel dalam lingkup global, tetapi dengan menggunakan metode ini kami membuatnya eksplisit (dan mudah dipahami) bukan memiliki definisi dari variabel yang tergantung di sana.

Arturo Ruiz Mañas
sumber
3

Masalah tautan yang Anda temui mungkin disebabkan oleh:

  • Memberikan definisi anggota kelas dan statis dalam file header,
  • Termasuk tajuk ini dalam dua atau lebih file sumber.

Ini adalah masalah umum bagi mereka yang memulai dengan C ++. Anggota kelas statis harus diinisialisasi dalam unit terjemahan tunggal yaitu dalam file sumber tunggal.

Sayangnya, anggota kelas statis harus diinisialisasi di luar tubuh kelas. Ini menyulitkan penulisan kode hanya header, dan oleh karena itu, saya menggunakan pendekatan yang sangat berbeda. Anda dapat memberikan objek statis Anda melalui fungsi kelas statis atau non-statis misalnya:

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

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};
tidak ada yang istimewa
sumber
1
Saya masih n00b lengkap sejauh C ++ berjalan, tapi ini terlihat cemerlang bagi saya, terima kasih banyak! Saya mendapatkan manajemen siklus hidup yang sempurna dari objek tunggal secara gratis.
Rafael Kitover
2

Salah satu cara "jadul" untuk mendefinisikan konstanta adalah dengan menggantinya dengan enum:

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

Cara ini tidak memerlukan memberikan definisi, dan menghindari membuat konstan lvalue , yang dapat menghemat beberapa sakit kepala, misalnya ketika Anda secara tidak sengaja ODR digunakan itu.

anatolyg
sumber
1

Saya hanya ingin menyebutkan sesuatu yang sedikit aneh bagi saya ketika saya pertama kali menemukan ini.

Saya perlu menginisialisasi anggota data statis pribadi di kelas templat.

di .h atau .hpp, terlihat seperti ini untuk menginisialisasi anggota data statis dari kelas templat:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;
Tyler Heers
sumber
0

Apakah ini sesuai dengan tujuan Anda?

//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;
}
David Nogueira
sumber