Statik veri üyeleri neden sınıf dışında C ++ 'ta (Java'dan farklı olarak) tanımlanmalı?


41
class A {
  static int foo () {} // ok
  static int x; // <--- needed to be defined separately in .cpp file
};

A::xAyrı ayrı bir .cpp dosyasında (veya şablonlar için aynı dosyada) tanımlanma gereği görmüyorum . Neden A::xaynı anda ilan edilemez ve tanımlanamaz?

Tarihsel nedenlerle yasaklandı mı?

Benim asıl sorum, eğer staticveri üyeleri aynı anda bildirilmiş / tanımlanmışsa ( Java ile aynı ) herhangi bir işlevselliği etkileyecek mi?


En iyi uygulama olarak, başlatma sırasındaki sorunları önlemek için statik değişkeninizi statik bir yöntemle (muhtemelen yerel statik olarak) sarmak genellikle daha iyidir.
Tamás Szelei

2
Bu kural aslında C ++ 11'de biraz rahatladı. const statik üyeleri genellikle artık tanımlanmak zorunda değildir. Bakınız: en.wikipedia.org/wiki/…
mirk

4
@ afishwhoswimsaround: Tüm durumlar için genel kurallar üzerinden belirtmek iyi bir fikir değildir (en iyi uygulamalar bağlamla uygulanmalıdır). Burada olmayan bir problemi çözmeye çalışıyorsun. Başlatma sırası sorunu yalnızca yapıcı olan ve diğer statik depolama süresi nesnelerine erişen nesneyi etkiler. 'X', int olduğu için, 'x' özel olduğundan ikincisi geçerli değildir. Üçüncüsü, bunun soru ile ilgisi yok.
Martin York

1
Yığın Taşması Üzerine
Monica ile Hafiflik Yarışları,

2
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};. En.cppreference.com/w/cpp/language/static#Static_data_members
Vladimir Reshetnikov

Yanıtlar:


15

Düşündüğünüz sınırlamanın anlambilim ile ilgili olmadığını (başlatma işlemi aynı dosyada tanımlanmışsa neden bir şey değiştirmeli?) Değil de geriye doğru uyumluluk nedenleriyle kolayca değiştirilemeyeceği için C ++ derleme modeline bağlı olduğunu düşünüyorum. ya çok karmaşık hale gelir (aynı zamanda yeni bir derleme modelini ve mevcut olanı aynı anda destekler) ya da mevcut kodu derlemeye izin vermez (yeni bir derleme modeli ekleyerek ve mevcut olanı bırakarak).

C ++ derleme modeli, (başlık) dosyalarını dahil ederek bildirimleri bir kaynak dosyaya içe aktardığınız C'nin modelinden kaynaklanır. Bu şekilde, derleyici, içerdiği tüm dosyaları ve bu dosyalardan içerilen tüm dosyaları yinelemeli olarak içeren tam bir büyük kaynak dosyayı görür. Bunun IMO'ya büyük bir avantajı vardır, yani derleyicinin uygulanmasını kolaylaştırır. Tabii ki, dahil edilen dosyalara herhangi bir şey yazabilirsiniz, yani hem bildirimler hem de tanımlar. Başlık dosyalarına açıklamaları ve .c veya .cpp dosyalarına tanımları koymak iyi bir uygulamadır.

Öte yandan, derleyicinin çok iyi bildiği bir derleme modeline sahip olmak, başka bir modülde tanımlanmış bir genel sembol bildirgesini ithal etmek mi yoksa, tarafından sağlanan bir genel sembol tanımını derlemek mi mümkündür? mevcut modül . Sadece ikinci durumda, derleyici bu sembolü (örneğin bir değişken) geçerli nesne dosyasına koymalıdır.

Örneğin, GNU Pascal'da şöylea bir dosyaya bir birim yazabilirsiniz a.pas:

unit a;

interface

var MyStaticVariable: Integer;

implementation

begin
  MyStaticVariable := 0
end.

burada global değişken aynı kaynak dosyada bildirilir ve başlatılır.

Ardından, a'yı alıp global değişkeni kullanan farklı birimleriniz olabilir MyStaticVariable, örneğin bir birim b ( b.pas):

unit b;

interface

uses a;

procedure PrintB;

implementation

procedure PrintB;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

ve bir c ( c.pas) birimi :

unit c;

interface

uses a;

procedure PrintC;

implementation

procedure PrintC;
begin
  Inc(MyStaticVariable);
  WriteLn(MyStaticVariable)
end;
end.

Sonunda b ve c birimlerini ana programda kullanabilirsiniz m.pas:

program M;

uses b, c;

begin
  PrintB;
  PrintC;
  PrintB
end.

Bu dosyaları ayrı ayrı derleyebilirsiniz:

$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas

ve sonra bir çalıştırılabilir dosya üret:

$ gpc -o m m.o a.o b.o c.o

ve çalıştırın:

$ ./m
1
2
3

Buradaki hile, derleyici bir program modülünde bir kullanım yönergesi gördüğünde (örn. B.pas'da bir kullanımda) karşılık gelen .pas dosyasını içermez, fakat önceden derlenmiş bir .gpi dosyasını arar. arayüz dosyası ( belgelere bakın ). Bu .gpidosyalar, derleyici tarafından .oher modül derlendiğinde dosyalar ile birlikte oluşturulur . Böylece genel sembol MyStaticVariablenesne dosyasında sadece bir kez tanımlanır a.o.

Java da benzer şekilde çalışır: derleyici A sınıfını B sınıfına alırsa, A sınıfına bakar ve dosyaya ihtiyaç duymaz A.java. Böylece A sınıfı için tüm tanımlamalar ve ilklendirmeler tek bir kaynak dosyaya konulabilir.

C ++ 'a geri dönersek, C ++' da statik veri üyelerini ayrı bir dosyada tanımlamak zorunda olmanızın nedeni, C ++ derleme modeliyle, linker veya derleyici tarafından kullanılan diğer araçların getirdiği sınırlamalardan daha fazladır. C ++ 'da bazı sembollerin içe aktarılması, bildirimlerini geçerli derleme biriminin bir parçası olarak oluşturmak anlamına gelir. Bu, şablonların derlenme biçimleri nedeniyle, diğer şeylerin yanı sıra, çok önemlidir. Ancak bu, dahil edilmiş bir dosyada herhangi bir genel sembolü (fonksiyonlar, değişkenler, yöntemler, statik veri üyeleri) tanımlayamayacağınız / tanımamamanız gerektiği anlamına gelir, aksi takdirde bu semboller derlenmiş nesne dosyalarında çarpılabilir.


42

Statik üyeler bir sınıfın TÜM örnekleri arasında paylaşıldığından, tek bir yerde tanımlanmaları gerekir. Gerçekten, bunlar erişim kısıtlamaları olan global değişkenlerdir.

Bunları başlıkta tanımlamaya çalışırsanız, bu başlığı içeren her modülde tanımlanır ve tüm yinelenen tanımları bulduğunda bağlantı sırasında hata alırsınız.

Evet, bu en azından kısmen Cfront'tan kalma tarihi bir konudur; bir çeşit gizli "static_members_of_everything.cpp" oluşturacak ve buna link verecek bir derleyici yazılabilir. Ancak, geriye dönük uyumluluktan kopacaktı ve bunu yapmanın gerçek bir faydası olmayacaktı.


2
Benim sorum şu anki davranışın nedeni değil, bu tür dil gramerinin gerekçesidir. Başka bir deyişle, staticdeğişkenlerin aynı yerde (Java gibi) bildirildiğini / tanımlandığını varsayalım, sonra ne yanlış gidebilir?
iammilind

8
@ iammilind Bu cevabın açıklaması nedeniyle gramerin gerekli olduğunu anlamadığınızı düşünüyorum. Şimdi neden C (ve C ++) 'nın derleme modelinden dolayı: c ve cpp dosyaları ayrı programlar gibi ayrı ayrı derlenen gerçek kod dosyasıdır, daha sonra tam çalıştırılabilir hale getirmek için birbirlerine bağlanırlar. Başlıklar, derleyici için gerçekten kod değil, yalnızca c ve cpp dosyalarının içine kopyalayıp yapıştırmak için yazılmış metinlerdir. Şimdi bir şey birkaç kez tanımlanırsa, onu derleyemez, aynı ada sahip birkaç yerel değişkeniniz varsa, aynı şekilde derleyemez.
Klaim

1
@Klaim, staticüyeler ne olacak template? Tüm başlık dosyalarında görünür olmaları gerektiği için izin verilir. Bu cevaba itiraz etmiyorum, ancak sorum da aynı şekilde uymuyor.
iammilind

@ iammilind şablonları gerçek kod değil, kod üreten kod. Bir şablonun her örneği, derleyici tarafından sağlanan her statik bildirimin bir ve yalnızca bir statik örneğine sahiptir. Örneği hala tanımlamanız gerekir, ancak örneğin bir şablonunu tanımladığınızda, yukarıda belirtildiği gibi gerçek kod değildir. Şablonlar, kelimenin tam anlamıyla, derleyicinin kod üretmesi için kullanılan kod şablonlarıdır.
Klaim

2
@iammilind: Şablonlar, statik değişkenleri de dahil olmak üzere her nesne dosyasında tipik olarak örneklenir. ELF nesne dosyalarına sahip Linux'ta, derleyici başlatmaları zayıf semboller olarak işaretler; bu, bağlayıcının aynı başlatmanın birden çok kopyasını birleştirdiği anlamına gelir. Aynı teknoloji başlık dosyalarında statik değişkenlerin tanımlanmasına izin vermek için kullanılabilir, bu yüzden yapılmamasının nedeni muhtemelen geçmiş sebeplerin ve derleme performansının bir araya gelmesidir. Derleme modelinin tamamı, bir sonraki C ++ standardı modülleri içerdiğinde umarım düzeltilecektir .
han,

6

Bunun olası nedeni, bu C ++ dilini, nesne dosyası ve bağlantı modelinin birden fazla tanımın birden fazla nesne dosyasından birleştirilmesini desteklemediği ortamlarda uygulanabilir olmasını sağlamasıdır.

Bir sınıf bildirimi (iyi nedenlerle bildirim olarak adlandırılır) birden fazla çeviri birimine alınır. Bildirgede statik değişkenler için tanım varsa, birden fazla çeviri biriminde birden fazla tanımla sonuçlanacaktı (Ve unutmayın, bu isimlerin dış bağlantıları vardır.)

Bu durum mümkündür, ancak bağlayıcının şikayet etmeden birden çok tanımı ele almasını gerektirir.

(Ve bunun, bir sembol türüne veya ne tür bir bölüme yerleştirildiğine göre yapılmadığı sürece, Tek Tanımlama Kuralı ile çakıştığını unutmayın.)


6

C ++ ve Java arasında büyük bir fark var.

Java, her şeyi kendi çalışma zamanı ortamında yaratan kendi sanal makinesi üzerinde çalışır. Bir tanım birden fazla kez görülürse, çalışma zamanı ortamı ultimatelenin bildiği aynı nesneye etki eder.

C ++ 'da "nihai bilgi sahibi" yoktur: C ++, C, Fortran Pascal vb. Kaynak kodundan (CPP dosyası) ara formata (OBJ dosyası veya ".o" dosyasına bağlı olarak "çevirmen" dir). OS) ifadelerin makine komutuna çevrildiği ve adların bir sembol tablosunun aracılık ettiği dolaylı adreslere dönüşmesi.

Derleyici tarafından değil, tüm OBJ'leri (geldikleri dil ne olursa olsun) bir araya getirerek, sembollere doğru olan tüm adresleri tekrar işaretleyerek bir program oluşturuyor. Etkili tanım.

Bağlayıcının çalışması yoluyla, bir tanım (bir değişken için fiziksel alanı yaratan şey) benzersiz olmalıdır.

C ++ 'ın kendi başına bağlantı oluşturmadığını ve linkerin C ++ özellikleri tarafından verilmediğini unutmayın: linker, OS modüllerinin oluşturulma biçimi nedeniyle mevcuttur (genellikle C ve ASM'de). C ++ olduğu gibi kullanmak zorunda.

Şimdi: bir başlık dosyası birkaç CPP dosyasına "yapıştırılacak" bir şey. Her bir CPP dosyası birbirinden bağımsız olarak çevrilmiştir. Farklı CPP dosyalarını çeviren bir derleyici, hepsi aynı tanımda olsun , tanımlanmış nesne için " yaratma kodunu " sonuçta ortaya çıkan tüm OBJ'lere yerleştirir.

Derleyici, tüm bu OBJ'lerin tek bir program oluşturmak için birlikte mi yoksa farklı bağımsız programlar oluşturmak için ayrı ayrı mı kullanılacağını bilemez (ve asla bilemez).

Bağlayıcı, nasıl ve neden tanımların olduğunu ve nereden geldiklerini bilmiyor (C ++ hakkında bile bilmiyor: her "statik dil", bağlantı için tanım ve referanslar üretebilir). Sadece verilen bir adreste "tanımlanmış" bir "sembol" referansının olduğunu bilir.

Belirli bir sembol için birden fazla tanım varsa (referanslarla tanımları birbirine karıştırmayın), linker onlarla ne yapılacağı hakkında hiçbir bilgiye sahip değildir (dilden bağımsız).

Büyük bir kasaba oluşturmak için birkaç şehri birleştirmek gibidir: iki " Zaman Meydanı " na sahipseniz ve dışarıdan gelen " Zaman Meydanı " na gitmek isteyen bir grup insan bulursanız, saf bir teknik temelde karar veremezsiniz. ( bu isimleri veren ve onları idare etmekle sorumlu olacak politika hakkında hiçbir bilgi olmadan ) bunların gönderileceği yer.


3
Java ve C ++ arasındaki genel sembollere göre fark, sanal bir makineye sahip Java ile değil, C ++ derleme modeliyle bağlantılıdır. Bu bakımdan Pascal ve C ++ 'ı aynı kategoriye koymam. Bunun yerine, C ve C ++ 'ı, Java ve Pascal (ve belki OCaml, Scala, Ada, vb.)' Nin aksine "ithal edilen beyanların dahil edildiği ve ana kaynak dosya ile birlikte derlendiği diller" olarak gruplayacağım. İçe aktarılan bildirimler, derleyici tarafından dışa aktarılan sembollerle ilgili bilgileri içeren önceden derlenmiş dosyalarda aranır ".
Giorgio

1
@Giorgio: Java referansı kabul edilmeyebilir, ancak Emilio'nun cevabının çoğunlukla sorunun özüne, yani ayrı bir derlemeden sonra nesne dosyası / linker aşamasına gelmekle doğru olduğunu düşünüyorum.
ixache 20:13

5

Gerekir çünkü aksi takdirde derleyici değişkeni nereye koyacağını bilmiyor. Her cpp dosyası ayrı ayrı derlenir ve diğerini bilmiyor. Bağlayıcı değişkenleri, işlevleri vb. Çözer. Kişisel olarak kararsız ve statik üyeler arasındaki farkın ne olduğunu göremiyorum (kararsızın hangi dosyada tanımlanacağını seçmek zorunda değiliz).

Derleyici yazarlarının bu şekilde uygulamasının daha kolay olduğunu düşünüyorum. Statik sınıf / yapı dışında var olabilir ve belki de tutarlılık nedeniyle ya da derleyici yazarları için bu kısıtlamayı standartlarda tanımladıklarını belirttiler.


2

Sanırım sebebi buldum. staticDeğişkenleri ayrı bir alanda tanımlamak , herhangi bir değere başlatılmasını sağlar. Başlamazsa, varsayılan olarak 0 olur.

C ++ 11'den önce, C ++ 'da sınıfta başlatmaya izin verilmedi. Yani biri şöyle yazamaz:

struct X
{
  static int i = 4;
};

Bu nedenle, şimdi değişkeni başlatmak için sınıfın dışına şunu yazmalısınız:

struct X
{
  static int i;
};
int X::i = 4;

Diğer cevaplarda da tartışıldığı gibi int X::i, artık bir global ve birçok dosyada global olarak bildirmek birden fazla sembol link hatasına neden oluyor.

Bu nedenle staticayrı bir çeviri birimi içinde bir sınıf değişkeni tanımlanması gerekir. Bununla birlikte, aşağıdaki yolun derleyiciye birden fazla sembol oluşturmaması talimatını vermesi gerektiği söylenebilir.

static int X::i = 4;
^^^^^^

0

A :: x yalnızca global bir değişkendir, ancak ad alanı A'ya ve erişim kısıtlamalarına sahiptir.

Birisi, başka herhangi bir global değişken gibi, bunu ilan etmek zorundadır ve bu, A kodunun kalanını içeren projeyle statik olarak bağlantılı bir projede bile yapılabilir.

Bunların hepsine kötü tasarım diyebilirim, ancak bu şekilde yararlanabileceğiniz birkaç özellik var:

  1. Yapıcı çağrı düzeni ... Bir int için önemli değil, ancak diğer statik veya global değişkenlere erişebilecek daha karmaşık bir üye için kritik olabilir.

  2. Statik başlatıcı - bir müşterinin A :: x'in başlatılması gerektiğine karar vermesine izin verebilirsiniz.

  3. c ++ ve c'de işaretçiler aracılığıyla belleğe tam erişiminiz olduğundan, değişkenlerin fiziksel konumu önemlidir. Bir değişkenin bağlantı nesnesindeki yerini temel alarak yararlanabileceğiniz çok yaramaz şeyler vardır.

Bu durumun ortaya çıkmasının "nedeni" olduğundan şüpheliyim. Muhtemelen C'nin C ++ 'a dönüşmesi ve şimdi dili değiştirmenizi engelleyen geriye dönük uyumluluk sorunu.


2
Bu, önceki 6
cevapta
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.