C ++ 'da özel statik üyeler nasıl başlatılır?


519

C ++ 'da özel, statik bir veri üyesini başlatmanın en iyi yolu nedir? Bunu başlık dosyamda denedim, ama bana garip linker hataları veriyor:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Sanırım bu dersin dışında özel bir üyeyi başlatamıyorum. Peki bunu yapmanın en iyi yolu nedir?


2
Merhaba Jason. Statik üyelerin (özellikle integral olanlar) varsayılan başlatılması hakkında bir yorum bulamadım. Aslında linker bulabilmesi için int foo :: i yazmanız gerekir, ancak otomatik olarak 0 ile başlatılacaktır! Bu satır yeterli olacaktır: int foo :: i; (Bu, statik bellekte depolanan tüm nesneler için geçerlidir, bağlayıcı statik nesneleri başlatmakla görevlidir.)
Nico

1
Aşağıdaki cevaplar bir şablon sınıfı için geçerli değildir. Diyorlar: Başlatma, kaynak dosyaya gitmelidir. Bir şablon sınıfı için bu ne mümkün ne de gerekli.
Joachim W

7
C ++ 17 (hatta tamsayı olmayan tipleri için) statik veri elemanlarının içi başlatma sağlar: inline static int x[] = {1, 2, 3};. Bkz. En.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Yanıtlar:


556

Sınıf bildirimi başlık dosyasında olmalıdır (Veya paylaşılmadıysa kaynak dosyada).
Dosya: foo.h

class foo
{
    private:
        static int i;
};

Ancak başlatma kaynak dosyasında olmalıdır.
Dosya: foo.cpp

int foo::i = 0;

Başlatma başlık dosyasındaysa, başlık dosyasını içeren her dosya statik üyenin bir tanımına sahip olacaktır. Bu nedenle, bağlantı aşaması sırasında, değişkeni başlatma kodu birden çok kaynak dosyada tanımlanacağı için bağlayıcı hataları alırsınız. Başlatma static int iişlemi herhangi bir fonksiyonun dışında yapılmalıdır.

Not: Matt Curtis: noktalar üzerinden C ++ sadeleştirilmesi izin verdiğini yukarıda statik üye değişkeni const int türü ise (örneğin int, bool, char). Daha sonra üye değişkenini doğrudan başlık dosyasındaki sınıf bildiriminin içinde bildirebilir ve başlatabilirsiniz:

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

4
Evet. Ama sorunun basitleştirildiğini varsayıyorum. Teknik olarak, bildirim ve tanımın tümü tek bir kaynak dosyasında olabilir. Ancak bu, sınıfın diğer sınıflar tarafından kullanımını sınırlar.
Martin York

11
aslında sadece POD değil, aynı zamanda bir int tipi olması gerekir (int, kısa, bool, char ...)
Matt Curtis

9
Bunun yalnızca değerin nasıl başlatıldığıyla ilgili bir soru olmadığını unutmayın: böyle tanımlanan const integral türleri, uygulama tarafından derleme zamanı sabitlerine dönüştürülebilir. Bu her zaman istediğiniz şey değildir, çünkü ikili bağımlılığı artırır: değer değişirse istemci kodunun yeniden derlenmesi gerekir.
Steve Jessop

5
@Martin: s / POD / integral tip / düzeltmesine ek olarak, adres daha önce alınmışsa bir tanım olması da gerekir. Kulağa tuhaf gelse de, sınıf tanımında başlatıcı ile yapılan açıklama bir tanım değildir. Templated const deyim bir başlık dosyasına tanımını ihtiyaç durumlar için geçici bir çözüm sağlar. Başka ve daha basit bir çözüm, yerel statik sabitin değerini üreten bir işlevdir. Şerefe ve hth.,
Şerefe ve hth. - Alf

3
İnt foo :: i = 0; bir işlevin içinde olmamalıdır (ana işlev dahil). Ana işlevimin başında aldım ve bundan hoşlanmıyor.
qwerty9967

89

Bir değişken için :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Bunun nedeni foo::i, programınızda yalnızca bir örneği olabilir . extern int iBir başlık dosyasında ve int ibir kaynak dosyada eşdeğerdir .

Bir sabit için değeri doğrudan sınıf bildirimine koyabilirsiniz:

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

2
Bu geçerli bir nokta. Bunu da açıklayacağım. Ancak bunun sadece POD tipleri için geçerli olduğuna dikkat edilmelidir.
Martin York

O zamandan beri, C ++ sınıf içi bildirimle sadece iyi olmasına izin verir ve integral tipleri için tanım yoktur. C ++ 98'in kendisi veya C ++ 03'ten beri veya ne zaman? Lütfen özgün bağlantıları paylaşın lütfen. C ++ standart ifadeleri derleyicilerle senkronize değildir. Üyenin kullanıldıklarında hala tanımlanacağından söz ederler. Yani, C ++ Standart alıntı olsa gerek yok
smRaj

1
privateDeğişkenlerin neden Sınıf dışında başlatılabileceğini merak ediyorum , bu statik olmayan değişkenler için de yapılabilir.
Krishna Oza

Açıklamayı buldun mu? @Krishna_Oza
nn0p

@ nn0p henüz değil, statik olmayan özel değişkenlerin dışarıdan başlatılması Cpp'de Classbir anlam ifade etmiyor.
Krishna Oza

41

C ++ 17 olduğundan, statik üyeler başlıkta satır içi anahtar kelimeyle tanımlanabilir.

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

"Statik veri üyesi satır içi olarak bildirilebilir. Sınıf tanımında satır içi statik veri üyesi tanımlanabilir ve varsayılan üye başlatıcısı belirtebilir. Sınıf dışı tanımlamaya gerek yoktur:"

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

1
Bu, şu anda yeni standart haline gelmekte olan C ++ 17'den beri mümkündür.
Grebu

31

Bu sorunun gelecekteki izleyicileri için, monkey0506'nın önerdiğinden kaçınmanız gerektiğini belirtmek istiyorum .

Başlık dosyaları bildirimler içindir.

Başlık dosyaları .cpp, doğrudan veya dolaylı olarak her dosya için bir kez derlenir #includesve daha önce herhangi bir fonksiyonun dışındaki kodlar program başlatma sırasında çalıştırılır main().

: foo::i = VALUE;Başlığına koyarak , her dosya için foo:ideğer VALUE(ne olursa olsun) atanır .cppve bu atamalar main()çalıştırılmadan önce (bağlayıcı tarafından belirlenen) belirsiz bir sırada gerçekleşir .

Dosyalarımızdan #define VALUEbirinde farklı bir sayı olursak ne olur .cpp? İyi derlenecek ve programı çalışana kadar hangisinin kazandığını bilmenin hiçbir yolu olmayacak.

Aynı nedenle sizin bu asla bir başlık içine yürütülen kod koymak asla #includebir .cppdosyaya.

(her zaman kullanmanız gerektiğini kabul eden) korumalar ekleyin: sizi #includetek bir .cppdosya derlerken aynı başlık dolaylı olarak birden çok kez olmak


2
Tabii ki bu konuda haklısın, bir sınıf şablonu dışında (ki bu sorulmuyor, ama ben çok uğraşıyorum). Sınıf tam olarak tanımlanmışsa ve sınıf şablonu değilse, bu statik üyeleri ayrı bir CPP dosyasına koyun, ancak sınıf şablonları için tanımın aynı çeviri biriminde (ör. Başlık dosyası) olması gerekir.
monkey0506

@ monkey_05_06: Bu, şablonlanmış kodda statik üyeden kaçınmak için bir argüman gibi görünüyor: Zaten sınıfın her örneği için bir statik üyeyle karşılaşıyorsunuz. büyük olasılıkla üstbilgiyi birden fazla cpp dosyasına derleyerek sorun daha da kötüleşir ... Çakışan tanımların bir salını alabilirsiniz.
Joshua Clayton

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/… Bu bağlantı, biraz yük yüklüyse daha temiz olan ana işlevde statik şablon üyelerini örneklemeyi gösterir.
Joshua Clayton

1
Argümanınız gerçekten çok büyük. İlk olarak, # isim tanımlayamazsınız çünkü makro adı geçerli bir tanımlayıcıdır. Ve yapabilseniz bile - bunu kim yapardı? Başlık dosyaları bildirim içindir -? Hadi .. Üstbilgiye değerler koymaktan kaçınmanız gereken tek durum tek kullanılanla savaşmaktır. Ve değeri başlığa koymak, değeri değiştirmeniz gerektiğinde gereksiz yeniden derlemeye neden olabilir.
Aleksander Fular

20

Bir Microsoft derleyicisi [1] ile, benzer olmayan statik değişkenler intbir başlık dosyasında, ancak Microsoft'a özel olarak sınıf bildiriminin dışında tanımlanabilir __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Bunun iyi olduğunu söylemiyorum, sadece yapılabileceğini söylüyorum.

[1] Bu günlerde, MSC desteğinden daha fazla derleyici __declspec(selectany)- en azından gcc ve clang. Belki daha da fazla.


17
int foo::i = 0; 

Değişkeni başlatmak için doğru sözdizimidir, ancak üstbilgiden ziyade kaynak dosyaya (.cpp) gitmelidir.

Statik bir değişken olduğu için, derleyicinin sadece bir kopyasını oluşturması gerekir. Bir satır "int foo: i" bazı kodunuzda derleyiciye nereye koymak için aksi takdirde bir bağlantı hatası almak gerekir. Bu bir başlık içindeyse, başlığı içeren her dosyada bir kopya alırsınız, bu yüzden bağlayıcıdan çarpım tanımlı sembol hataları alın.


12

Burada bunu yorum olarak eklemek için yeterli temsilcim yok, ancak IMO, başlıklarınızı #include muhafızları ile yazmak için iyi bir stildir , Paranaix tarafından birkaç saat önce belirtildiği gibi, çoklu tanım hatasını önleyecektir. Zaten ayrı bir CPP dosyası kullanmadığınız sürece, statik olmayan tümleşik üyeleri başlatmak için bir tane kullanmak gerekmez.

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

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Bunun için ayrı bir CPP dosyası kullanmaya gerek yok. Tabii ki yapabilirsin, ama bunu yapmak için teknik bir neden yok.


21
#include korumaları, çeviri birimi başına birden fazla tanımlamayı önler.
Paul Fultz II

3
iyi tarzı ile ilgili: kapanış endif hakkında yorum eklemeniz gerekir:#endif // FOO_H
Riga

9
Bu yalnızca foo.h içeren tek bir derleme biriminiz varsa işe yarar. İki veya daha fazla cpp tipik bir durum olan foo.h içeriyorsa, her bir cpp aynı statik değişkeni bildirir; tüm cpp'leri içeren tek bir dosya). Ancak paket derleme harika olmasına rağmen, sorunun çözümü bir cpp içinde (int foo :: i = 0;) ilan etmektir!
Alejadro Xalabarder

1
Veya sadece kullanın#pragma once
tambre

12

Bazı bileşik türlerini (fe string) başlatmak istiyorsanız, bunun gibi bir şey yapabilirsiniz:

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

Yöntemin ListInitializationGuardiçindeki statik bir değişken olduğu için , SomeClass::getList()yalnızca bir kez oluşturulacaktır; Bu, initialize _listihtiyacınız olan değere değişecektir . Daha sonra yapılan herhangi bir çağrı, getListzaten başlatılmış _listnesneyi döndürecektir .

Tabii ki _listnesneye her zaman çağrı getList()yöntemiyle erişmek zorundasınız .


1
Bu deyimin üye nesnesi başına bir yöntem oluşturmayı gerektirmeyen bir sürümü: stackoverflow.com/a/48337288/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

9

Birden çok nesne için çalışan C ++ 11 statik oluşturucu deseni

Bir deyim şu adreste önerildi: https://stackoverflow.com/a/27088552/895245 ama burada üye başına yeni bir yöntem oluşturulmasını gerektirmeyen daha temiz bir sürüm var.

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 akış yukarı .

Derleyin ve çalıştırın:

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

Ayrıca bakınız: C ++ statik yapıcılar? Özel statik nesneleri başlatmam gerekiyor

Ubuntu 19.04'te test edildi.

C ++ 17 satır içi değişkeni

Burada belirtildiği gibi: https://stackoverflow.com/a/45062055/895245 ama burada daha da net hale getirmek için çok dosyalı çalıştırılabilir bir örnek: Satır içi değişkenler nasıl çalışır?


5

Üstbilgi korumaları kullanıyorsanız atamayı üstbilgi dosyasına da ekleyebilirsiniz. Bu tekniği yarattığım bir C ++ kütüphanesi için kullandım. Aynı sonucu elde etmenin başka bir yolu da statik yöntemler kullanmaktır. Örneğin...

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

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

Yukarıdaki kodun bir CPP / kaynak dosyası gerektirmeme "bonusu" vardır. Yine, C ++ kütüphanelerim için kullandığım bir yöntem.


4

Fikri Karl'dan takip ediyorum. Beğendim ve şimdi de kullanıyorum. Gösterimi biraz değiştirdim ve bazı işlevler ekledim

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

bu çıktılar

mystatic value 7
mystatic value 3
is my static 1 0

3

Ayrıca privateStatic.cpp dosyasında çalışma:

#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

3

Bir set_default()yöntem ne olacak ?

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

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

Sadece set_default(int x)yöntemi kullanmak zorunda kalırız ve staticdeğişkenimiz başlatılır.

Bu, yorumların geri kalanıyla anlaşmazlık içinde değildir, aslında değişkeni küresel bir kapsamda başlatma ilkesini izler, ancak bu yöntemi kullanarak, tanımı yerine açık (ve kolay anlaşılır) hale getiririz orada asılı değişken.


3

Karşılaştığınız bağlayıcı sorununa muhtemelen şunlar neden olabilir:

  • Üstbilgi dosyasında hem sınıf hem de statik üye tanımı sağlamak,
  • Bu üstbilgiyi iki veya daha fazla kaynak dosyasına dahil etmek.

Bu, C ++ ile başlayanlar için yaygın bir sorundur. Statik sınıf üyesi tek çeviri biriminde, yani tek kaynak dosyasında başlatılmalıdır.

Ne yazık ki, statik sınıf üyesi sınıf gövdesi dışında başlatılmalıdır. Bu, yalnızca başlık kodunu yazmayı zorlaştırıyor ve bu nedenle oldukça farklı bir yaklaşım kullanıyorum. Statik nesnenizi statik veya statik olmayan sınıf işlevi aracılığıyla sağlayabilirsiniz, örneğin:

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

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

1
Ben hala C ++ gider kadar tam bir n00b, ama bu bana parlak görünüyor, çok teşekkür ederim! Singleton nesnesinin ücretsiz olarak mükemmel bir yaşam döngüsü yönetimi elde ediyorum.
Rafael Kitover

2

Sabitleri tanımlamanın bir "eski okul" yolu, bunları aşağıdakilerle değiştirmektir enum:

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

Bu şekilde bir tanım sağlamanız gerekmez ve sabit bir değer yaratmaktan kaçınır , bu da size bazı baş ağrılarını kurtarabilir, örneğin yanlışlıkla ODR kullandığınızda .


1

Bununla ilk karşılaştığımda bana biraz garip bir şeyden bahsetmek istedim.

Şablon sınıfında özel bir statik veri üyesi başlatmak gerekiyordu.

.h veya .hpp dosyasında, bir şablon sınıfının statik veri üyesini başlatmak için şöyle görünür:

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

0

Bu sizin amacınıza hizmet ediyor mu?

//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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.