Sebelum C ++ 11, kita hanya dapat melakukan inisialisasi di dalam kelas pada anggota const statis integral atau tipe enumerasi. Stroustrup membahas hal ini dalam C ++ FAQ-nya , memberikan contoh berikut:
class Y {
const int c3 = 7; // error: not static
static int c4 = 7; // error: not const
static const float c5 = 7; // error: not integral
};
Dan alasan berikut:
Jadi mengapa ada batasan yang tidak nyaman ini? 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.
Namun, C ++ 11 melonggarkan pembatasan ini, memungkinkan inisialisasi anggota non-statis di kelas (§12.6.2 / 8):
Dalam konstruktor non-delegasi, jika anggota data non-statis atau kelas dasar tertentu tidak ditetapkan oleh mem-initializer-id (termasuk kasus di mana tidak ada mem-initializer-list karena konstruktor tidak memiliki ctor-initializer ) dan entitas tersebut bukan kelas basis virtual dari kelas abstrak (10.4), maka
- jika entitas adalah anggota data non-statis yang memiliki penginisialisasi brace-or-equal , entitas diinisialisasi seperti yang ditentukan dalam 8.5;
- sebaliknya, jika entitas adalah anggota varian (9.5), tidak ada inisialisasi yang dilakukan;
- jika tidak, entitas diinisialisasi secara default (8.5).
Bagian 9.4.2 juga memungkinkan inisialisasi di dalam kelas dari anggota statis non-const jika mereka ditandai dengan constexpr
penentu.
Jadi apa yang terjadi dengan alasan pembatasan yang kami miliki di C ++ 03? Apakah kita hanya menerima "aturan linker yang rumit" atau ada perubahan lain yang membuat ini lebih mudah untuk diterapkan?
sumber
Jawaban:
Jawaban singkatnya adalah mereka menjaga linker tetap sama, dengan mengorbankan membuat compiler lebih rumit dari sebelumnya.
Yaitu, alih-alih ini menghasilkan beberapa definisi untuk disortir oleh linker, itu masih hanya menghasilkan satu definisi, dan kompiler harus mengatasinya.
Ini juga mengarah pada aturan yang agak lebih kompleks bagi programmer untuk tetap disortir juga, tetapi sebagian besar cukup sederhana sehingga itu bukan masalah besar. Aturan tambahan masuk saat Anda memiliki dua penginisialisasi berbeda yang ditentukan untuk satu anggota:
class X { int a = 1234; public: X() = default; X(int z) : a(z) {} };
Sekarang, aturan tambahan pada titik ini berhubungan dengan nilai apa yang digunakan untuk menginisialisasi
a
ketika Anda menggunakan konstruktor non-default. Jawabannya cukup sederhana: jika Anda menggunakan konstruktor yang tidak menetapkan nilai lain, maka1234
akan digunakan untuk menginisialisasia
- tetapi jika Anda menggunakan konstruktor yang menetapkan beberapa nilai lain, maka pada1234
dasarnya diabaikan.Sebagai contoh:
#include <iostream> class X { int a = 1234; public: X() = default; X(int z) : a(z) {} friend std::ostream &operator<<(std::ostream &os, X const &x) { return os << x.a; } }; int main() { X x; X y{5678}; std::cout << x << "\n" << y; return 0; }
Hasil:
1234 5678
sumber
Saya kira penalaran itu mungkin telah ditulis sebelum templat diselesaikan. Setelah semua "aturan linker rumit" yang diperlukan untuk inisialisasi anggota statis di kelas sudah diperlukan untuk C ++ 11 untuk mendukung anggota statis template.
Mempertimbangkan
struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed, // thanks @Kapil for pointing that out // vs. template <class T> struct B { static int s; } template <class T> int B<T>::s = ::ComputeSomething(); // or template <class T> void Foo() { static int s = ::ComputeSomething(); s++; std::cout << s << "\n"; }
Masalah untuk kompilator adalah sama dalam ketiga kasus: di unit terjemahan manakah ia harus memancarkan definisi
s
dan kode yang diperlukan untuk menginisialisasinya? Solusi sederhananya adalah memancarkannya ke mana-mana dan biarkan linker memilahnya. Itulah mengapa penaut sudah mendukung hal-hal seperti__declspec(selectany)
. Ini tidak akan mungkin untuk mengimplementasikan C ++ 03 tanpa itu. Dan itulah mengapa tidak perlu memperpanjang linker.Lebih jelasnya: Saya pikir alasan yang diberikan dalam standar lama benar-benar salah.
MEMPERBARUI
Seperti yang ditunjukkan Kapil, contoh pertama saya bahkan tidak diizinkan dalam standar saat ini (C ++ 14). Saya tetap membiarkannya, karena IMO adalah kasus tersulit untuk implementasi (compiler, linker). Maksud saya adalah: bahkan yang terjadi tidak lebih keras daripada apa yang sudah diperbolehkan misalnya saat menggunakan template.
sumber
Secara teori,
So why do these inconvenient restrictions exist?...
alasan itu valid tetapi dapat dengan mudah dilewati dan inilah yang dilakukan C ++ 11.Saat Anda menyertakan file, itu hanya menyertakan file dan mengabaikan inisialisasi apa pun. Anggota diinisialisasi hanya saat Anda membuat instance kelas.
Dengan kata lain, inisialisasi masih terikat dengan konstruktor, hanya saja notasinya berbeda dan lebih nyaman. Jika konstruktor tidak dipanggil, nilainya tidak diinisialisasi.
Jika konstruktor dipanggil, nilai akan diinisialisasi dengan in-class in-class jika ada atau konstruktor dapat menimpanya dengan inisialisasi sendiri. Jalur inisialisasi pada dasarnya sama, yaitu melalui konstruktor.
Ini terbukti dari FAQ Stroustrup sendiri di C ++ 11.
sumber
Y::c3
dalam pertanyaan? Seperti yang saya pahami,c3
akan selalu diinisialisasi kecuali ada konstruktor yang menimpa default yang diberikan dalam deklarasi.