Referensi yang tidak terdefinisi untuk anggota kelas statis

201

Adakah yang bisa menjelaskan mengapa kode berikut tidak dapat dikompilasi? Setidaknya pada g ++ 4.2.4.

Dan yang lebih menarik, mengapa itu akan dikompilasi ketika saya melemparkan ANGGOTA ke int?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}
Pawel Piatkowski
sumber
Saya mengedit pertanyaan untuk membuat indentasi kode sebanyak empat spasi alih-alih menggunakan <pre> <code> </code> </pre>. Ini berarti kurung sudut tidak diartikan sebagai HTML.
Steve Jessop
stackoverflow.com/questions/16284629/… Anda dapat merujuk ke pertanyaan ini.
Tooba Iqbal

Jawaban:

199

Anda harus benar-benar mendefinisikan anggota statis di suatu tempat (setelah definisi kelas). Coba ini:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

Itu harus menyingkirkan referensi yang tidak ditentukan.

Drew Hall
sumber
3
Bagusnya, inisialisasi static integer integer inline menciptakan konstanta bilangan bulat yang tidak dapat Anda ambil alamatnya, dan vektor mengambil param referensi.
Evan Teran
10
Jawaban ini hanya membahas bagian pertama dari pertanyaan. Bagian kedua jauh lebih menarik: Mengapa menambahkan pemain NOP membuatnya bekerja tanpa memerlukan deklarasi eksternal?
nobar
32
Saya hanya menghabiskan sedikit waktu mencari tahu bahwa jika definisi kelas dalam file header, maka alokasi variabel statis harus dalam file implementasi, bukan header.
shanet
1
@ Shanet: Poin yang sangat bagus - saya seharusnya menyebutkan itu dalam jawaban saya!
Drew Hall
Tetapi jika saya mendeklarasikannya sebagai const, apakah tidak mungkin bagi saya untuk mengubah nilai variabel itu?
Namratha
78

Masalahnya muncul karena bentrokan menarik dari fitur C ++ baru dan apa yang Anda coba lakukan. Pertama, mari kita lihat push_backtanda tangannya:

void push_back(const T&)

Itu mengharapkan referensi ke objek tipe T. Di bawah sistem inisialisasi yang lama, anggota semacam itu ada. Sebagai contoh, kode berikut mengkompilasi dengan baik:

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

Ini karena ada objek aktual di suatu tempat yang memiliki nilai yang tersimpan di dalamnya. Namun, jika Anda beralih ke metode baru menentukan anggota const statis, seperti yang Anda miliki di atas,Foo::MEMBER tidak lagi menjadi objek. Ini konstan, agak mirip dengan:

#define MEMBER 1

Tetapi tanpa sakit kepala makro preprocessor (dan dengan keamanan jenis). Itu berarti bahwa vektor, yang mengharapkan referensi, tidak bisa mendapatkannya.

Douglas Mayle
sumber
2
terima kasih, itu telah membantu ... yang dapat memenuhi syarat untuk stackoverflow.com/questions/1995113/strangest-language-feature jika belum ada ...
Andre Holzner
1
Juga patut dicatat bahwa MSVC menerima versi non-cor tanpa keluhan.
porges
4
-1: Ini tidak benar. Anda masih harus mendefinisikan anggota statis yang diinisialisasi sebaris, ketika mereka digunakan di suatu tempat. Optimalisasi kompiler yang dapat menyingkirkan kesalahan linker Anda tidak mengubah itu. Dalam hal ini konversi nilai-ke-nilai Anda (terima kasih kepada para (int)pemeran) terjadi di unit terjemahan dengan visibilitas sempurna dari konstanta, dan Foo::MEMBERtidak lagi digunakan odr . Ini berbeda dengan pemanggilan fungsi pertama, di mana referensi diedarkan dan dievaluasi di tempat lain.
Lightness Races dalam Orbit
Bagaimana dengan void push_back( const T& value );? const&Dapat mengikat dengan nilai.
Kostas
59

Standar C ++ memerlukan definisi untuk anggota const statis Anda jika definisi tersebut diperlukan.

Definisi tersebut diperlukan, misalnya jika alamatnya digunakan. push_backmengambil parameternya dengan referensi const, dan oleh karena itu compiler memerlukan alamat anggota Anda dan Anda perlu mendefinisikannya di namespace.

Ketika Anda secara eksplisit melemparkan konstanta, Anda sedang membuat sementara dan ini sementara yang terikat pada referensi (di bawah aturan khusus dalam standar).

Ini adalah kasus yang sangat menarik, dan saya benar-benar berpikir itu layak mengangkat masalah sehingga std diubah untuk memiliki perilaku yang sama untuk anggota tetap Anda!

Meskipun, dalam cara yang aneh ini bisa dilihat sebagai penggunaan sah operator '+' unary. Pada dasarnya hasil dari nilai unary +adalah rvalue dan karenanya aturan untuk mengikat nilai rujukan ke referensi const berlaku dan kami tidak menggunakan alamat anggota const statis kami:

v.push_back( +Foo::MEMBER );
Richard Corden
sumber
3
+1. Ya itu tentu aneh bahwa untuk objek x tipe T, ekspresi "(T) x" dapat digunakan untuk mengikat const ref sementara plain "x" tidak bisa. Saya suka pengamatan Anda tentang "unary +"! Siapa yang akan berpikir bahwa miskin sedikit "unary +" benar-benar memiliki penggunaan sebuah ... :)
j_random_hacker
3
Berpikir tentang kasus umum ... Apakah ada jenis objek lain di C ++ yang memiliki properti yang (1) dapat digunakan sebagai nilai hanya jika telah didefinisikan tetapi (2) dapat dikonversi ke nilai tanpa didefinisikan?
j_random_hacker
Pertanyaan bagus, dan setidaknya saat ini saya tidak dapat memikirkan contoh lain. Ini mungkin hanya di sini karena panitia kebanyakan hanya menggunakan kembali sintaksis yang ada.
Richard Corden
@RichardCorden: bagaimana unary + menyelesaikannya?
Blood-HaZaRd
1
@ Blood-HaZaRd: Sebelum referensi nilai satu-satunya kelebihan push_backadalah const &. Menggunakan anggota secara langsung mengakibatkan anggota terikat pada referensi, yang mengharuskannya memiliki alamat. Namun, menambahkan +menciptakan sementara dengan nilai anggota. Referensi kemudian mengikat untuk sementara itu daripada mengharuskan anggota memiliki alamat.
Richard Corden
10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;
iso9660
sumber
1

Tidak tahu mengapa pemeran bekerja, tetapi Foo :: MEMBER tidak dialokasikan hingga pertama kali Foo dimuat, dan karena Anda tidak pernah memuatnya, itu tidak pernah dialokasikan. Jika Anda memiliki referensi ke Foo di suatu tempat, itu mungkin akan berhasil.

Paul Tomblin
sumber
Saya pikir Anda menjawab pertanyaan Anda sendiri: Para pemain bekerja karena ia menciptakan referensi (sementara).
Jaap Versteegh
1

Dengan C ++ 11, hal di atas dimungkinkan untuk tipe dasar sebagai

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

Bagian ini constexprmenciptakan ekspresi statis yang bertentangan dengan variabel statis - dan itu berperilaku seperti definisi metode inline yang sangat sederhana. Pendekatan ini terbukti sedikit goyah dengan constexprs C-string di dalam kelas template, meskipun.

mulai
sumber
Ini ternyata penting bagi saya, karena "static const int MEMBER = 1;" diperlukan untuk menggunakan ANGGOTA dalam sakelar, sedangkan deklarasi eksternal diperlukan untuk menggunakannya dalam vektor, dan Anda tidak dapat memiliki keduanya sekaligus. Tetapi ekspresi yang Anda berikan di sini tidak bekerja untuk keduanya, setidaknya dengan kompiler saya.
Ben Farmer
0

Mengenai pertanyaan kedua: push_ref mengambil referensi sebagai parameter, dan Anda tidak dapat memiliki referensi untuk memeber const statis dari kelas / struct. Setelah Anda memanggil static_cast, variabel sementara dibuat. Dan referensi ke objek ini dapat dilewati, semuanya berfungsi dengan baik.

Atau setidaknya kolega saya yang menyelesaikan ini mengatakan demikian.

Quarra
sumber