Mengapa saya tidak dapat menginisialisasi anggota statis non-const atau array statis di kelas?

116

Mengapa saya tidak dapat menginisialisasi non-const staticmember atau staticarray di kelas?

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

kompilator mengeluarkan kesalahan berikut:

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]’

Saya punya dua pertanyaan:

  1. Mengapa saya tidak dapat menginisialisasi staticanggota data di kelas?
  2. Mengapa saya tidak bisa menginisialisasi staticarray di kelas, bahkan constarray?
Yishu Fang
sumber
1
Saya pikir alasan utamanya adalah sulit untuk melakukan yang benar. Pada prinsipnya, Anda mungkin dapat melakukan apa yang Anda bicarakan, tetapi akan ada beberapa efek samping yang aneh. Seperti jika contoh array Anda diizinkan, maka Anda mungkin bisa mendapatkan nilai A :: c [0], tetapi tidak bisa meneruskan A :: c ke fungsi karena itu akan membutuhkan alamat, dan waktu kompilasi konstanta tidak memiliki alamat. C ++ 11 telah mengaktifkan beberapa di antaranya dengan menggunakan constexpr.
Vaughn Cato
Pertanyaan bagus dan jawaban buatan. Tautan yang membantu saya: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

Jawaban:

144

Mengapa saya tidak dapat menginisialisasi staticanggota data di kelas?

Standar C ++ hanya mengizinkan jenis integral konstanta statis atau enumerasi untuk diinisialisasi di dalam kelas. Inilah alasannya adiizinkan untuk diinisialisasi sementara yang lain tidak.

Referensi:
C ++ 03 9.4.2 Anggota data statis
§4

Jika anggota data statis adalah dari integral konst atau jenis enumerasi konst, deklarasinya dalam definisi kelas dapat menentukan penginisialisasi konstan yang akan menjadi ekspresi konstanta integral (5.19). Dalam hal ini, anggota dapat muncul dalam ekspresi konstanta integral. Anggota masih harus ditentukan dalam ruang lingkup namespace jika digunakan dalam program dan definisi ruang lingkup namespace tidak boleh berisi penginisialisasi.

Apa tipe integral?

C ++ 03 3.9.1 Tipe dasar
§7

Tipe bool, char, wchar_t, dan tipe integer bertanda tangan dan tak bertanda tangan secara kolektif disebut tipe integral.43) Sinonim dari tipe integral adalah tipe integer.

Catatan kaki:

43) Oleh karena itu, pencacahan (7.2) tidak terpisahkan; namun, enumerasi dapat dipromosikan menjadi int, unsigned int, long, atau unsigned long, seperti ditentukan dalam 4.5.

Solusi:

Anda bisa menggunakan trik enum untuk menginisialisasi array di dalam definisi kelas Anda.

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

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

};

Mengapa Standar tidak mengizinkan ini?

Bjarne menjelaskan hal ini dengan tepat di sini :

Kelas biasanya dideklarasikan dalam file header dan file header biasanya disertakan ke dalam banyak unit terjemahan. Namun, untuk menghindari aturan linker yang rumit, C ++ mengharuskan setiap objek memiliki definisi yang unik. Aturan itu akan rusak jika C ++ mengizinkan definisi entitas di kelas yang perlu disimpan dalam memori sebagai objek.

Mengapa hanya static consttipe integral & enum yang diperbolehkan In-class Initialization?

Jawabannya tersembunyi dalam kutipan Bjarne. Bacalah dengan seksama,
"C ++ mengharuskan setiap objek memiliki definisi yang unik. Aturan itu akan rusak jika C ++ mengizinkan definisi entitas dalam kelas yang perlu disimpan dalam memori sebagai objek."

Perhatikan bahwa hanya static constbilangan bulat yang dapat diperlakukan sebagai konstanta waktu kompilasi. Kompilator mengetahui bahwa nilai integer tidak akan berubah setiap saat dan karenanya ia dapat menerapkan sihirnya sendiri dan menerapkan pengoptimalan, kompilator hanya menyebariskan anggota kelas seperti itu, yaitu mereka tidak disimpan dalam memori lagi, Karena kebutuhan untuk disimpan dalam memori telah dihapus , itu memberikan variabel seperti pengecualian untuk aturan yang disebutkan oleh Bjarne.

Perlu dicatat di sini bahwa meskipun static constnilai integral dapat memiliki In-Class Initialization, mengambil alamat variabel tersebut tidak diperbolehkan. Seseorang dapat mengambil alamat dari anggota statis jika (dan hanya jika) memiliki definisi di luar kelas. Ini selanjutnya memvalidasi alasan di atas.

enum diperbolehkan ini karena nilai dari tipe enumerasi dapat digunakan di mana int diharapkan. lihat kutipan di atas


Bagaimana ini berubah di C ++ 11?

C ++ 11 melonggarkan pembatasan sampai batas tertentu.

C ++ 11 9.4.2 Anggota data statis
§3

Jika anggota data statis berjenis const literal, deklarasinya dalam definisi kelas dapat menentukan brace-or-equal-initializer di mana setiap klausa penginisialisasi yang merupakan ekspresi-penugasan adalah ekspresi konstan. Anggota data statis tipe literal dapat dideklarasikan dalam definisi kelas dengan constexpr specifier;jika demikian, deklarasinya harus menentukan brace-or-equal-initializer di mana setiap klausa penginisialisasi yang merupakan ekspresi penugasanadalah ekspresi konstan. [Catatan: Dalam kedua kasus ini, anggota mungkin muncul dalam ekspresi konstan. —End note] Anggota masih harus didefinisikan dalam lingkup namespace jika digunakan dalam program dan definisi ruang lingkup namespace tidak boleh berisi penginisialisasi.

Selain itu, C ++ 11 akan mengizinkan (§12.6.2.8) anggota data non-statis diinisialisasi di tempat ia dideklarasikan (di kelasnya). Ini berarti semantik pengguna yang lebih mudah.

Perhatikan bahwa fitur-fitur ini belum diimplementasikan di gcc 4.7 terbaru, Jadi Anda mungkin masih mendapatkan kesalahan kompilasi.

Alok Save
sumber
7
Hal-hal berbeda di c ++ 11. Jawaban bisa menggunakan update.
bames53
4
Ini tampaknya tidak benar: "Perhatikan bahwa hanya bilangan bulat konstanta statis yang dapat diperlakukan sebagai konstanta waktu kompilasi. Kompilator tahu bahwa nilai bilangan bulat tidak akan berubah kapan saja dan karenanya ia dapat menerapkan sihirnya sendiri dan menerapkan pengoptimalan, kompilator cukup inlines anggota kelas tersebut yaitu, mereka tidak disimpan dalam memori lagi ," Apakah Anda yakin mereka tentu tidak disimpan dalam memori? Bagaimana jika saya memberikan definisi untuk anggota? Apa yang akan &memberkembali?
Nawaz
2
@Als: Ya. Itulah pertanyaan saya. Jadi mengapa C ++ mengizinkan inisialisasi dalam kelas hanya untuk tipe integral, tidak dijawab dengan benar oleh jawaban Anda. Pikirkan mengapa tidak mengizinkan inisialisasi untuk static const char*anggota?
Nawaz
3
@Nawaz: Karena C ++ 03 hanya mengizinkan penginisialisasi konstan untuk integral statis dan konstanta dan jenis enumerasi konst dan tidak ada jenis lain, C ++ 11 memperluas ini ke jenis literal konstan yang melonggarkan norma untuk In-Class Initialization. di C ++ 03 mungkin merupakan pengawasan yang menjamin perubahan dan karenanya diperbaiki di C ++ 11, jika ada alasan taktis tradisional untuk perubahan, saya tidak menyadarinya.Jika Anda mengetahui ada, silakan berbagi mereka.
Alok Save
4
Par "Solusi" yang Anda sebutkan tidak berfungsi dengan g ++.
iammilind
4

Ini tampaknya merupakan peninggalan dari masa lalu penaut sederhana. Anda dapat menggunakan variabel statis dalam metode statis sebagai solusi:

// 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();

dan

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

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

dan

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

membangun:

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

Lari:

./main

Fakta bahwa ini berfungsi (secara konsisten, meskipun definisi kelas disertakan dalam unit kompilasi yang berbeda), menunjukkan bahwa linker saat ini (gcc 4.9.2) sebenarnya cukup pintar.

Lucu: Sidik jari 0123di lengan dan 3210di x86.

bukan-pengguna
sumber
1

Saya pikir itu untuk mencegah Anda mencampurkan deklarasi dan definisi. (Pikirkan tentang masalah yang dapat terjadi jika Anda menyertakan file di banyak tempat.)

pengguna541686
sumber
0

Itu karena hanya ada satu definisi A::ayang digunakan semua unit terjemahan.

Jika Anda tampil static int a = 3;di kelas dalam tajuk yang termasuk dalam semua unit terjemahan maka Anda akan mendapatkan banyak definisi. Oleh karena itu, definisi non-out-of-line dari statis secara paksa membuat kesalahan kompiler.

Menggunakan static inlineatau static constmemperbaikinya. static inlinehanya mengkonkretkan simbol jika digunakan dalam unit terjemahan dan memastikan penaut hanya memilih dan meninggalkan satu salinan jika ditentukan di beberapa unit terjemahan karena berada dalam grup comdat. constpada ruang lingkup file membuat kompilator tidak pernah memancarkan simbol karena itu selalu diganti segera dalam kode kecuali externdigunakan, yang tidak diizinkan di kelas.

Satu hal yang perlu diperhatikan adalah static inline int b;diperlakukan sebagai definisi sedangkan static const int batau static const A b;masih diperlakukan sebagai deklarasi dan harus didefinisikan out-of-line jika Anda tidak mendefinisikannya di dalam kelas. Menariknya static constexpr A b;diperlakukan sebagai definisi, sedangkan static constexpr int b;kesalahan dan harus memiliki penginisialisasi (ini karena mereka sekarang menjadi definisi dan seperti definisi const / constexpr pada ruang lingkup file, mereka memerlukan penginisialisasi yang int tidak memiliki tetapi tipe kelas tidak karena memiliki implisit = A()ketika itu adalah definisi - clang memungkinkan ini tetapi gcc mengharuskan Anda untuk menginisialisasi secara eksplisit atau ini adalah kesalahan. Ini bukan masalah dengan sebaris). static const A b = A();tidak diperbolehkan dan harus constexpratauinlineuntuk mengizinkan penginisialisasi objek statis dengan tipe kelas yaitu membuat anggota statis dari tipe kelas lebih dari sekedar deklarasi. Jadi ya dalam situasi tertentu A a;tidak sama dengan inisialisasi eksplisit A a = A();(yang pertama bisa berupa deklarasi tetapi jika hanya deklarasi yang diizinkan untuk jenis itu maka yang terakhir adalah kesalahan. Yang terakhir hanya dapat digunakan pada definisi. constexprMenjadikannya definisi ). Jika Anda menggunakan constexprdan menetapkan konstruktor default, maka konstruktor tersebut harusconstexpr

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

Anggota statis adalah deklarasi cakupan file langsung extern int A::a;(yang hanya dapat dibuat di kelas dan definisi di luar baris harus merujuk ke anggota statis dalam kelas dan harus berupa definisi dan tidak boleh berisi eksternal) sedangkan anggota non-statis adalah bagian dari definisi tipe lengkap kelas dan memiliki aturan yang sama dengan deklarasi cakupan file tanpa extern. Mereka adalah definisi yang implisit. Begitu int i[]; int i[5];juga redefinisi dimana static int i[]; int A::i[5];tidak tetapi tidak seperti 2 externs, kompilator masih akan mendeteksi anggota duplikat jika Anda melakukannya static int i[]; static int i[5];di kelas.

Lewis Kelsey
sumber
-3

variabel statis dikhususkan untuk kelas. Konstruktor menginisialisasi atribut TERUTAMA untuk sebuah instance.


sumber