Sınıfta const olmayan statik üyeyi veya statik diziyi neden başlatamıyorum?


116

Neden bir sınıfta const olmayan staticüyeyi veya staticdiziyi başlatamıyorum ?

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

derleyici aşağıdaki hataları verir:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member b
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type int [2]’

İki sorum var:

  1. staticVeri üyelerini neden sınıftaki başlatamıyorum ?
  2. Neden dizideki staticdizileri, hatta constdiziyi başlatamıyorum ?

1
Bence asıl sebep, doğru yapmanın zor olması. Prensip olarak, muhtemelen bahsettiğiniz şeyi yapabilirsiniz, ancak bazı tuhaf yan etkiler olabilir. Örneğin dizi örneğinize izin verilmişse, o zaman A :: c [0] değerini alabilirsiniz, ancak bir işleve A :: c'yi geçiremezsiniz çünkü bu bir adres ve derleme zamanı gerektirir sabitlerin bir adresi yok. C ++ 11, constexpr kullanımıyla bunların bir kısmını etkinleştirdi.
Vaughn Cato

Harika soru ve uydurma cevap. Bana yardımcı olan bağlantı: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Yanıtlar:


144

Neden staticsınıftaki veri üyelerini başlatamıyorum ?

C ++ standardı yalnızca statik sabit integral veya numaralandırma türlerinin sınıf içinde başlatılmasına izin verir. Bu nedenle a, diğerlerinin başlatılmasına izin verilmiyor.

Referans:
C ++ 03 9.4.2 Statik veri üyeleri
§4

Bir statik veri üyesi, const integrali veya sabit numaralandırma türündeyse, sınıf tanımındaki bildirimi, bir integral sabit ifadesi (5.19) olacak bir sabit başlatıcıyı belirtebilir. Bu durumda üye, integral sabit ifadelerde görünebilir. Üye, programda kullanılıyorsa yine de bir ad alanı kapsamında tanımlanacaktır ve ad alanı kapsam tanımı bir başlatıcı içermeyecektir.

İntegral türleri nelerdir?

C ++ 03 3.9.1 Temel türler
§7

Bool, char, wchar_t türleri ve işaretli ve işaretsiz tamsayı türleri topluca integral türleri olarak adlandırılır.43) İntegral türünün eşanlamlısı, tamsayı türüdür.

Dipnot:

43) Bu nedenle, numaralandırmalar (7.2) integral değildir; ancak, numaralandırmalar, 4.5'te belirtildiği gibi int, unsigned int, long veya unsigned long olarak yükseltilebilir.

Çözüm:

Sınıf tanımınızın içinde bir diziyi başlatmak için enum numarasını kullanabilirsiniz .

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

Standart buna neden izin vermiyor?

Bjarne bunu uygun bir şekilde burada açıklıyor :

Bir sınıf genellikle bir başlık dosyasında bildirilir ve bir başlık dosyası genellikle birçok çeviri birimine dahil edilir. Ancak, karmaşık bağlayıcı kurallarından kaçınmak için, C ++ her nesnenin benzersiz bir tanıma sahip olmasını gerektirir. C ++, bellekte nesneler olarak depolanması gereken varlıkların sınıf içi tanımına izin verirse, bu kural bozulur.

Neden yalnızca static constintegral türlerine ve numaralandırmalara Sınıf İçi Başlatma'ya izin verilir?

Cevap, Bjarne'nin sözünde gizli, yakından okuyun,
"C ++ her nesnenin benzersiz bir tanıma sahip olmasını gerektirir. C ++, bellekte nesne olarak depolanması gereken varlıkların sınıf içi tanımına izin verirse bu kural bozulur."

Yalnızca static consttam sayıların derleme zamanı sabitleri olarak değerlendirilebileceğini unutmayın. Derleyici, tamsayı değerinin hiçbir zaman değişmeyeceğini bilir ve bu nedenle kendi sihrini uygulayabilir ve optimizasyonları uygulayabilir, derleyici bu tür sınıf üyelerini satır içine alır, yani artık bellekte depolanmaz, bellekte depolanma ihtiyacı ortadan kalktıkça , bu tür değişkenlere Bjarne tarafından bahsedilen kuralın istisnasını verir.

Burada, static constintegral değerlerin Sınıf İçi Başlatma'ya sahip olabilmesine rağmen, bu tür değişkenlerin adreslerinin alınmasına izin verilmediğini belirtmek önemlidir . Statik bir üyenin adresi sınıf dışı bir tanıma sahipse (ve ancak) alınabilir. Bu, yukarıdaki gerekçeyi daha da doğrular.

numaralandırılmış türden değerler, girişlerin beklendiği yerde kullanılabileceğinden, buna izin verilir. yukarıdaki alıntıya bakın


Bu C ++ 11'de nasıl değişiyor?

C ++ 11, kısıtlamayı bir ölçüde gevşetir.

C ++ 11 9.4.2 Statik veri üyeleri
§3

Statik veri elemanı const hazır türdeyse, sınıf tanımında da bildirimi bir belirtebilir bağ ya da-eşit-başlatıcı her hangi başlatıcı-maddesi , bir olan atama-sentezleme sabit bir ifadesidir. Değişmez türden bir statik veri üyesi, sınıf tanımında, constexpr specifier;eğer öyleyse, bildirimi , bir atama ifadesi olan her başlatıcı cümlesinin bulunduğu bir parantez veya eşitleme başlatıcısı belirtmelidir.sabit bir ifadedir. [Not: Her iki durumda da üye sürekli ifadelerde görünebilir. —Son notu] Üye, programda kullanılıyorsa yine de bir ad alanı kapsamında tanımlanacaktır ve ad alanı kapsam tanımı bir başlatıcı içermeyecektir.

Ayrıca, C ++ 11 , statik olmayan bir veri üyesinin (kendi sınıfında) bildirildiği yerde başlatılmasına (§12.6.2.8) izin verecektir. Bu, çok kolay kullanıcı semantiği anlamına gelecektir.

Bu özelliklerin en son gcc 4.7'de henüz uygulanmadığına dikkat edin. Bu nedenle, derleme hataları almaya devam edebilirsiniz.


7
C ++ 11'de işler farklıdır. Cevap güncellemeyi kullanabilir.
bames53

4
Bu doğru görünmüyor: "Yalnızca statik sabit tam sayıların derleme zamanı sabitleri olarak ele alınabileceğini unutmayın. Derleyici, tamsayı değerinin hiçbir zaman değişmeyeceğini bilir ve bu nedenle kendi sihrini uygulayabilir ve optimizasyonları uygulayabilir, derleyici basitçe Bu tür sınıf üyelerini satır içi olarak sıralar, yani artık bellekte depolanmazlar , " Mutlaka bellekte depolanmadıklarından emin misiniz ? Üyeler için tanımlar verirsem ne olur? Ne &memberdönecekti?
Nawaz

2
@Als: Evet. Benim sorum bu. Öyleyse neden C ++ yalnızca integral türleri için sınıf içi başlatmaya izin veriyor, cevabınız doğru şekilde yanıtlanmıyor. static const char*Üye için başlatmaya neden izin vermediğini bir düşünün ?
Nawaz

3
@Nawaz: C ++ 03 yalnızca statik ve const integral ve sabit numaralandırma türü için sabit başlatıcıya izin verdiğinden ve başka tür olmadığından, C ++ 11 bunu Sınıf İçi Başlatma için normları gevşeten sabit bir değişmez türe genişletir. C ++ 03'te, belki de bir değişikliği garanti eden ve dolayısıyla C ++ 11'de düzeltilen bir gözetimdi, eğer değişikliğin herhangi bir geleneksel taktik nedeni varsa, bunların farkında değilim. onları.
Alok Save

4
Bahsettiğiniz "Geçici Çözüm" kısmı g ++ ile çalışmıyor .
iammilind

4

Bu, basit bağlayıcıların eski günlerinden kalma bir kalıntı gibi görünüyor. Statik değişkenleri statik yöntemlerde geçici çözüm olarak kullanabilirsiniz:

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

ve

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

ve

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

inşa etmek:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

Çalıştırmak:

./main

Bunun işe yaraması (tutarlı bir şekilde, sınıf tanımı farklı derleme birimlerinde bulunsa bile), bugünkü bağlayıcının (gcc 4.9.2) aslında yeterince akıllı olduğunu göstermektedir.

Komik: Kolda 0123ve x86'da yazdırır 3210.


1

Bence beyanları ve tanımları karıştırmanızı engellemek için. (Dosyayı birden çok yere eklerseniz ortaya çıkabilecek sorunları düşünün.)


0

Bunun nedeni A::a, tüm çeviri birimlerinin kullandığı tek bir tanım olabilir .

static int a = 3;Tüm çeviri birimlerinde bulunan bir başlıktaki bir sınıfta performans gösterdiyseniz , birden çok tanım alırsınız. Bu nedenle, statik olmayan satır dışı tanım, zorla bir derleyici hatası yapılır.

Bunu kullanmak static inlineveya static constçare bulmak. static inlinesembolü yalnızca çeviri biriminde kullanılıyorsa somutlaştırır ve bağlayıcının bir comdat grubunda olması nedeniyle birden çok çeviri biriminde tanımlanmışsa yalnızca bir kopya seçmesini ve bırakmasını sağlar. constdosya kapsamında derleyicinin hiçbir zaman bir sembol yaymamasını sağlar, çünkü externkullanılmadığı sürece her zaman kodda hemen değiştirilir , bu da bir sınıfta izin verilmez.

Unutulmaması gereken bir şey static inline int b;, bir tanım olarak kabul edilirken static const int bveya static const A b;hala bir bildirim olarak kabul edilir ve sınıf içinde tanımlamazsanız satır dışı olarak tanımlanmalıdır. İlginç static constexpr A b;bir şekilde bir tanım olarak ele alınır, oysa static constexpr int b;bir hatadır ve bir başlatıcıya sahip olması gerekir (bunun nedeni, artık tanım haline gelmeleridir ve dosya kapsamındaki herhangi bir const / constexpr tanımı gibi, bir int'in sahip olmadığı ancak bir sınıf türü olan bir başlatıcıya ihtiyaç duymalarıdır. yapar çünkü = A()bir tanım olduğunda örtük bir özelliği vardır - clang buna izin verir, ancak gcc açıkça başlatmanızı gerektirir veya bu bir hatadır. Bu, bunun yerine satır içi ile ilgili bir sorun değildir). static const A b = A();izin verilmez ve olmalı constexprveyainlinesınıf tipine sahip statik bir nesne için bir başlatıcıya izin vermek için, yani sınıf türünün statik bir üyesini bir bildirimden daha fazla yapmak için. Yani, bazı durumlarda evet, A a;açıkça başlatmayla aynı şey değildir A a = A();(ilki bir bildirim olabilir, ancak bu tür için yalnızca bir bildirime izin veriliyorsa, ikincisi bir hatadır. İkincisi yalnızca bir tanımda kullanılabilir. constexprOnu bir tanım yapar ). constexprVarsayılan bir kurucu kullanır ve belirtirseniz, kurucununconstexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

Statik üye, doğrudan bir dosya kapsamı bildirimidir extern int A::a;(yalnızca sınıfta yapılabilir ve satır dışı tanımlamalar bir sınıftaki statik bir üyeye başvurmalıdır ve tanımlar olmalıdır ve extern içeremez), ancak statik olmayan bir üye, bir sınıfın tam tür tanımı ve dosya kapsamı bildirimleriyle aynı kurallara sahip değil extern. Örtük tanımlardır. Yani int i[]; int i[5];bir yeniden tanımlamadır, static int i[]; int A::i[5];ancak 2 externs'den farklı olarak, sınıfta yaparsanız, derleyici yine de yinelenen bir üye algılayacaktır static int i[]; static int i[5];.


-3

statik değişkenler bir sınıfa özgüdür. Oluşturucular, bir örnek için ESPECIALY özniteliklerini başlatır.

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.