C ++ 11 memungkinkan inisialisasi anggota non-statis dan non-const di dalam kelas. Apa yang berubah?

89

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 constexprpenentu.

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?

Joseph Mansfield
sumber
5
Tidak ada yang terjadi. Kompiler telah berkembang lebih pintar dengan semua template hanya header ini sehingga ekstensi relatif mudah sekarang.
Öö Tiib
Cukup menarik pada IDE saya ketika saya memilih pra C ++ 11 kompilasi saya diizinkan untuk menginisialisasi anggota integral konstanta non-statis
Dean P

Jawaban:

67

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 aketika Anda menggunakan konstruktor non-default. Jawabannya cukup sederhana: jika Anda menggunakan konstruktor yang tidak menetapkan nilai lain, maka 1234akan digunakan untuk menginisialisasi a- tetapi jika Anda menggunakan konstruktor yang menetapkan beberapa nilai lain, maka pada 1234dasarnya 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
Jerry Coffin
sumber
1
Sepertinya ini sangat mungkin sebelumnya. Itu hanya membuat pekerjaan menulis kompiler lebih sulit. Apakah itu pernyataan yang adil?
allyourcode
10
@allyourcode: Ya dan tidak. Ya, itu membuat penulisan kompiler lebih sulit. Tetapi tidak, karena itu juga membuat penulisan spesifikasi C ++ menjadi sedikit lebih sulit.
Jerry Coffin
Apakah ada perbedaan cara menginisialisasi anggota kelas: int x = 7; atau int x {7} ;?
mbaros
9

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 sdan 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.

Paul Groke
sumber
Sayangnya ini tidak mendapatkan suara positif, karena banyak dari fitur C ++ 11 serupa di kompiler yang sudah menyertakan kapabilitas atau pengoptimalan yang diperlukan.
Alex Court
@AlexCourt Saya menulis jawaban ini baru-baru ini. Pertanyaan dan jawaban Jerry berasal dari tahun 2012. Jadi saya rasa itulah mengapa jawaban saya tidak mendapat banyak perhatian.
Paul Groke
1
Ini tidak akan memenuhi "struct A {static int s = :: ComputeSomething ();}" karena hanya konstanta statis yang dapat diinisialisasi di kelas
PapaDiHatti
8

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.

zar
sumber
Re "Jika konstruktor tidak dipanggil, nilai tidak diinisialisasi": Bagaimana saya bisa menghindari inisialisasi anggota Y::c3dalam pertanyaan? Seperti yang saya pahami, c3akan selalu diinisialisasi kecuali ada konstruktor yang menimpa default yang diberikan dalam deklarasi.
Peter - Pulihkan Monica