Statik sabit dize (sınıf üyesi)


445

Bir sınıf için özel bir statik sabite sahip olmak istiyorum (bu durumda bir şekil fabrikası).

Böyle bir şeye sahip olmak istiyorum.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

Ne yazık ki C ++ (g ++) derleyicisinden her türlü hatayı alıyorum, örneğin:

ISO C ++ 'RECTANGLE' üyesinin başlatılmasını yasaklıyor

integral olmayan 'std :: string' türündeki statik veri üyesinin geçersiz sınıf içi başlatılması

hata: 'DİKDÖRTGEN' statik yapma

Bu bana bu tür bir üye tasarımının standarda uygun olmadığını söyler. Bir #define yönergesi kullanmak zorunda kalmadan özel bir değişmez sabitiniz (veya belki de genel) nasıl var (Veri globitesinin çirkinliğinden kaçınmak istiyorum!)

Herhangi bir yardım takdir.


15
Tüm harika cevaplarınız için teşekkürler! Yaşasın SO!
£

Birisi bana 'integral' tipin ne olduğunu söyleyebilir mi? Çok teşekkür ederim.
£

1
İntegral tipleri, tamsayı sayılarını temsil eden tipleri ifade eder. Bkz publib.boulder.ibm.com/infocenter/comphelp/v8v101/...
bleater

Fabrikanızdaki özel statik dize iyi bir çözüm değildir - fabrika istemcilerinizin hangi şekillerin desteklendiğini bilmek zorunda olduğunu düşünün, bu yüzden özel statikte tutmak yerine, bunları statik const olarak ayrı bir ad alanına yerleştirin std :: string RECTANGLE = "Dikdörtgen ".
LukeCodeBaker

sınıfınız bir şablon sınıfı ise, bkz. stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Yanıtlar:


471

Statik üyenizi sınıf tanımının dışında tanımlamanız ve başlatıcıyı orada sağlamanız gerekir.

İlk

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

ve sonra

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

Başlangıçta kullanmaya çalıştığınız sözdizimine (sınıf tanımı içinde başlatıcı) yalnızca integral ve enum türlerinde izin verilir.


C ++ 17'den başlayarak, orijinal beyanınıza oldukça benzeyen başka bir seçeneğiniz var: satır içi değişkenler

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

Ek bir tanımlamaya gerek yoktur.

Ya da bunun yerine bu varyantta constbeyan edebilirsiniz constexpr. Açıkça inlinegerekli olmayacaktır, çünkü constexprima eder inline.


8
Ayrıca, bir STL dizesi kullanma zorunluluğu yoksa, sadece bir const char * tanımlayabilirsiniz. (daha az ek yük)
KSchmidt

50
Her zaman daha az ek yük olduğundan emin değilim - kullanıma bağlı. Bu üyenin const dizesini alan işlevlere argüman olarak iletilmesi isteniyorsa, başlatma sırasında her çağrı için bir dize nesnesi oluşturmaya karşı geçici olarak oluşturulur. Statik bir dize nesnesi oluşturmak için IMHO yükü ihmal edilebilir.
Tadeusz Kopec

23
Ben de her zaman std :: string's kullanmak istiyorum. Havai önemsiz olmakla birlikte, çok daha fazla seçenek var ve çok daha az "sihirli" == A :: RECTANGLE sadece karşılaştırmak için kendi adrese ... gibi bazı aptal şeyler yazmayı muhtemeldir
Matthieu M.

9
char const*tüm dinamik başlatma tamamlanmadan başlatılır o iyilik vardır. Yani herhangi bir nesnenin yapıcısında, RECTANGLEo zaman alreay başlatılmasına güvenebilirsiniz .
Johannes Schaub - litb

8
@cirosantilli: Çünkü C ++ başlatıcıları kez başından bölümleriydi tanımları değil, beyanlar . Ve sınıf içindeki veri üyesi beyanı sadece şudur: bir beyan. (Öte yandan, const integrali ve enum üyeleri ve C ++ 11'de - değişmez türlerin const üyeleri için bir istisna yapıldı .)
AnT

153

C ++ 11'de artık şunları yapabilirsiniz:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

31
Ne yazık ki bu çözüm std :: string için çalışmaz.
HelloWorld

2
Unutmayın 1. bu sadece değişmez değerlerle çalışır ve 2. bu standart uygun değildir, ancak Gnu / GCC cezaları tamamlasa da, diğer derleyiciler hata verecektir. Tanım bedende olmalıdır.
ManuelSchneid3r

2
@ ManuelSchneid3r Bu "standart değil" tam olarak nasıl? Bana göre bataklık standart C ++ 11 destek veya eşit başlatma .
underscore_d

3
@rvighne, hayır bu yanlış. var için constexprima eder const, tip için işaret etmez. Yani static constexpr const char* constile aynı static constexpr const char*, ama aynı değil static constexpr char*.
midenok

2
@ abyss.7 - Cevabınız için teşekkürler, lütfen bir tane daha var: Neden statik olması gerekiyor?
Guy Avraham

34

Sınıf tanımlarına İçinde yalnızca olabilir beyan statik üyeleri. Olmalılar tanımlanan sınıf dışında. Derleme zamanı integral sabitleri için standart, üyeleri "başlatabileceğiniz" istisnası oluşturur. Yine de bu bir tanım değil. Örneğin, adresin tanımı yapılmadan işe yaramaz.

Ben sabitler için const char [] üzerinde std :: string kullanmanın yarar görmedim bahsetmek istiyorum . std :: string güzel ve dinamik başlatma gerektiriyor. Yani, şöyle bir şey yazarsanız

const std::string foo = "hello";

ad alanı kapsamında foo yapıcısı ana başlatma işleminden hemen önce çalıştırılır ve bu kurucu yığın belleğinde sabit "merhaba" nın bir kopyasını oluşturur. Gerçekten bir std :: string olmak için RECTANGLE gerekmedikçe, aynı zamanda yazabilirsiniz

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

Orada! Öbek ayırma yok, kopyalama yok, dinamik başlatma yok.

Şerefe, s.


1
Bu C ++ 11 öncesi cevaptır. Standart C ++ kullanın ve std :: string_view kullanın.

1
C ++ 11'de std :: string_view yoktur.
Lukas Salich

17

Bu sadece ek bilgilerdir, ancak dizeyi başlık dosyasında gerçekten istiyorsanız, aşağıdaki gibi bir şey deneyin:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Yine de tavsiye şüphe rağmen.


Bu harika görünüyor :) - im c ++ dışında diğer dillerde bir arka plan var sanırım?
£

5
Ben tavsiye etmem. Bunu sık sık yapıyorum. İyi çalışıyor ve ben uygulama dosyasına dize koymak daha açık buluyorum. Std :: string gerçek verileri yine de yığın üzerinde bulunur. Ben const char * dönecekti, bu durumda bildirimi daha az yer (kod akıllıca) alacak böylece statik değişkeni bildirmek gerekmez. Sadece bir zevk meselesi.
Zoomulator

15

C ++ 17'de satır içi değişkenleri kullanabilirsiniz :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Bunun uçurumdan farklı olduğunu unutmayın.7 Bu, gerçek bir std::stringnesneyi tanımlar ,const char*


Kullanmanın inlineçok fazla kopya oluşturacağını düşünmüyor musunuz ?
shuva


8

Bu sınıf içi başlatma sözdizimini kullanmak için sabit, sabit bir ifade tarafından başlatılan integral veya numaralandırma türünün statik bir sabiti olmalıdır.

Kısıtlama budur. Bu nedenle, bu durumda sınıf dışında bir değişken tanımlamanız gerekir. @AndreyT'den yanıtlamaya bakın


7

Sınıf statik değişkenleri başlıkta bildirilebilir , ancak tanımlanmalıdır bir .cpp dosyasında . Bunun nedeni, statik değişkenin yalnızca bir örneği olabileceğidir ve derleyici, hangi nesne nesnesini içine koyacağına karar veremez, bunun yerine kararı vermeniz gerekir.

C ++ 11'deki bildirimle statik bir değerin tanımını korumak için iç içe bir statik yapı kullanılabilir. Bu durumda statik üye bir yapıdır ve bir .cpp dosyasında tanımlanması gerekir, ancak değerler başlıktadır.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

Tek tek üyeleri başlatmak yerine, tüm statik yapı .cpp'de başlatılır:

A::_Shapes A::shape;

Değerlere şununla erişilir:

A::shape.RECTANGLE;

veya - üyeler özel olduklarından ve yalnızca A'dan -

shape.RECTANGLE;

Bu çözümün statik değişkenlerin başlatılma sırasından hala muzdarip olduğunu unutmayın. Bir statik değer başka bir statik değişkeni başlatmak için kullanıldığında, ilki henüz başlatılmamış olabilir.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

Bu durumda statik değişken başlıkları , bağlayıcı tarafından oluşturulan başlatma sırasına bağlı olarak {""} veya {".h", ".hpp"} içerecektir.

@ Abyss.7 tarafından belirtildiği gibi constexpr, değişkenin değeri derleme zamanında hesaplanabiliyorsa da kullanabilirsiniz . Ancak, dizelerinizi ile bildirirseniz static constexpr const char*ve programınız std::stringbaşka türlü std::stringkullanırsa, böyle bir sabit kullandığınızda yeni bir nesne oluşturulacağı için bir ek yük olacaktır:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}

İyi hazırlanmış cevap Marko. İki ayrıntı: biri statik sınıf üyeleri için cpp dosyalarına ihtiyaç duymaz ve ayrıca her türlü sabit için lütfen std :: string_view kullanın.

4

Mevcut standart sadece statik sabit integral tipleri için böyle bir başlatmaya izin verir. Bu yüzden AndreyT'ın açıkladığı gibi yapmalısınız. Ancak, bu yeni üye başlatma sözdizimi aracılığıyla bir sonraki standartta sağlanacaktır .


4

mümkün sadece yapmak:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

veya

#define RECTANGLE "rectangle"

11
Yazılan bir sabit kullanılabilir olduğunda #define kullanmak yanlıştır.
Artur Czajka

İlk örneğiniz temelde iyi bir çözümdür, constexprancak statik bir işlev yapamazsınız const.
Frank Puffer

Bu çözümden kaçınılmalıdır. Her çağırmada yeni bir dize oluşturur. Bu daha iyi olurdu:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon

Tam dolu konteynırı neden iade değeri olarak kullanıyorsunuz? Std :: string_vew kullanın .. içeriği bu durumda geçerli kalacaktır. daha iyi dize görünümleri yapmak ve döndürmek için dize değişmezlerini kullanın ... ve son olarak, en önemlisi, const dönüş değerinin burada bir anlamı veya etkisi yoktur ..ah evet, ve bunu bir satırda, statik değil, bazı üstbilgilerde var adlandırılmış ad alanı ... ve lütfen constexpr olun

4

const char*Yukarıda belirtilen çözüme gidebilirsiniz , ancak daha sonra her zaman dizeye ihtiyacınız varsa, çok fazla yükünüz olacaktır.
Öte yandan, statik dize dinamik başlatma gerektirir, bu nedenle değerini başka bir global / statik değişkenin başlatılması sırasında kullanmak isterseniz, başlatma sırası sorununa çarpabilirsiniz. Bundan kaçınmak için, en ucuz şey, nesnenin başlatılıp başlatılmadığını kontrol eden bir alıcı aracılığıyla statik dize nesnesine erişmektir.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

Sadece kullanmayı unutmayın A::getS(). Herhangi bir iş parçacığı yalnızca tarafından başlatılabildiği main()ve A_s_initializeddaha önce başlatıldığı için main(), çok iş parçacıklı bir ortamda bile kilitlere ihtiyacınız yoktur. A_s_initialized(dinamik başlatmadan önce) varsayılan olarak 0'dır, bu nedenlegetS() s başlatılmadan önce , init işlevini güvenli bir şekilde çağırırsınız.

Btw, yukarıdaki yanıtta: " static const std :: string RECTANGLE () const ", statik işlevler, constzaten herhangi bir nesne varsa durumu değiştiremedikleri için olamaz (bu işaretçi yoktur).


4

2018 ve C ++ 17'ye hızlı ileri sarın.

  • std :: string kullanmayın, std :: string_view değişmezlerini kullanın
  • lütfen 'constexpr' feryatına dikkat edin. Bu aynı zamanda bir "derleme zamanı" mekanizmasıdır.
  • satır içi yok, tekrar anlamına gelmez
  • bunun için hiçbir cpp dosyası gerekli değildir
  • static_assert 'derleme' yalnızca derleme zamanında

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

Yukarıda uygun ve yasal bir standart C ++ vatandaşıdır. Herhangi bir ve tüm std :: algoritmaları, kapsayıcılar, yardımcı programlar ve benzerlerine kolayca dahil olabilir. Örneğin:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

Standart C ++ 'ın keyfini çıkarın


Kullanım std::string_viewkullanmak yalnızca sabitleri için string_viewtüm fonksiyonları parametreleri. İşlevlerinizden herhangi biri const std::string&parametre kullanıyorsa , string_viewbu parametreden bir sabit ilettiğinizde dizenin bir kopyası oluşturulur . Sabitleriniz türdeyse std::string, kopyalar ne const std::string&parametreler ne de std::string_viewparametreler için oluşturulmaz .
Marko Mahnič

Güzel cevap, ama string_view neden bir fonksiyondan döndürülüyor merak? Bu tür bir numara, inlinedeğişkenler ODR semantikleriyle C ++ 17'ye gelmeden önce kullanışlıdır . Ancak string_view de C ++ 17'dir, bu yüzden sadece constexpr auto some_str = "compile time"sv;iş yapar (ve aslında, bir değişken değildir, bu constexpryüzden inlineörtülüdür; bir değişkeniniz varsa - yani hayır constexpr- o inline auto some_str = "compile time"sv;zaman tabii ki bir ad alanı kapsamı yapar) Aslında küresel bir değişken olan değişken nadiren iyi bir fikir olabilir).
Kayıp Zihinselliği
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.