Mendefinisikan anggota integer const statis dalam definisi kelas

109

Pemahaman saya adalah bahwa C ++ memungkinkan anggota konstanta statis untuk didefinisikan di dalam kelas selama itu adalah tipe integer.

Lalu, mengapa kode berikut memberi saya kesalahan penaut?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

Kesalahan yang saya dapatkan adalah:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

Menariknya, jika saya mengomentari panggilan ke std :: min, kode mengkompilasi dan tautan dengan baik (meskipun test :: N juga direferensikan pada baris sebelumnya).

Tahu apa yang terjadi?

Kompiler saya adalah gcc 4.4 di Linux.

Komandan Tinggi4
sumber
3
Bekerja dengan baik pada Visual Studio 2010.
Puppy
4
Kesalahan persis ini dijelaskan di gcc.gnu.org/wiki/…
Jonathan Wakely
Dalam kasus tertentu char, Anda dapat mendefinisikannya sebagai constexpr static const char &N = "n"[0];. Perhatikan &. Saya kira ini berfungsi karena string literal didefinisikan secara otomatis. Saya agak khawatir tentang ini - ini mungkin berperilaku aneh di file header di antara unit terjemahan yang berbeda, karena string mungkin akan berada di beberapa alamat berbeda.
Aaron McDaid
1
Pertanyaan ini adalah manifestasi dari betapa buruknya jawaban C ++ untuk "jangan gunakan #defines untuk konstanta" masih.
Johannes Overmann
1
@JohannesOvermann Dalam hal ini, saya ingin menyebutkan penggunaan inline untuk variabel global sejak C ++ 17 inline const int N = 10, yang menurut pengetahuan saya masih memiliki penyimpanan di suatu tempat yang ditentukan oleh linker. Kata kunci sebaris juga dapat digunakan dalam kasus ini untuk memberikan definisi variabel statis di dalam pengujian definisi kelas.
Wormer

Jawaban:

72

Pemahaman saya adalah bahwa C ++ memungkinkan anggota konstanta statis untuk didefinisikan di dalam kelas selama itu adalah tipe integer.

Anda agak benar. Anda diizinkan untuk menginisialisasi integral konstanta statis dalam deklarasi kelas, tetapi itu bukan definisi.

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

Menariknya, jika saya mengomentari panggilan ke std :: min, kode mengkompilasi dan tautan dengan baik (meskipun test :: N juga direferensikan pada baris sebelumnya).

Tahu apa yang terjadi?

std :: min mengambil parameternya dengan referensi const. Jika mengambil nilai, Anda tidak akan mengalami masalah ini, tetapi karena Anda memerlukan referensi, Anda juga memerlukan definisi.

Berikut pasal / ayatnya:

9.4.2 / 4 - Jika anggota staticdata adalah constintegral atau consttipe enumerasi, 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 .

Lihat jawaban Chu untuk solusi yang mungkin.

Edward Strange
sumber
Begitu, itu menarik. Dalam hal ini, apa perbedaan antara memberikan nilai pada titik deklarasi versus memberikan nilai pada titik definisi? Yang mana yang direkomendasikan?
Komandan Tinggi4
Saya yakin Anda bisa lolos tanpa definisi selama Anda tidak pernah benar-benar "menggunakan" variabel tersebut. Jika Anda hanya menggunakannya sebagai bagian dari ekspresi konstan maka variabel tidak pernah digunakan. Jika tidak, tampaknya tidak akan ada perbedaan besar selain dapat melihat nilai di header - yang mungkin atau mungkin tidak sesuai dengan keinginan Anda.
Edward Strange
2
Jawaban singkatnya adalah static const x = 1; adalah nilai r tetapi bukan nilai l. Nilai tersebut tersedia sebagai konstanta pada waktu kompilasi (Anda dapat menentukan dimensi array dengannya) static const y; [tanpa penginisialisasi] harus ditentukan dalam file cpp dan dapat digunakan sebagai rvalue atau lvalue.
Dale Wilson
2
Alangkah baiknya jika mereka bisa memperpanjang / meningkatkan ini. objek yang diinisialisasi tetapi tidak didefinisikan harus, menurut pendapat saya, diperlakukan sama dengan literal. Misalnya, kita diizinkan untuk mengikat literal 5ke a const int&. Jadi mengapa tidak memperlakukan OP test::Nsebagai literal yang sesuai?
Aaron McDaid
Penjelasan yang menarik, terima kasih! Artinya di C ++ static const int masih belum ada pengganti integer #defines. enum selalu hanya bertanda int, jadi kita harus menggunakan kelas enum untuk konstanta individual. Akan sangat jelas bagi saya untuk merosotkan deklarasi konstan dengan nilai konstan dan tahu menjadi konstanta literal yang cara ini akan disusun tanpa masalah. C ++ memiliki jalan panjang ...
Johannes Overmann
51

Contoh Bjarne Stroustrup dalam C ++ FAQ-nya menunjukkan bahwa Anda benar, dan hanya perlu definisi jika Anda mengambil alamatnya.

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

Dia mengatakan "Anda dapat mengambil alamat dari anggota statis jika (dan hanya jika) memiliki definisi di luar kelas" . Yang menunjukkan itu akan berhasil sebaliknya. Mungkin fungsi min Anda memanggil alamat di balik layar.

HostileFork mengatakan jangan percaya SE
sumber
2
std::minmengambil parameternya dengan referensi, itulah sebabnya definisi diperlukan.
Rakete1111
Bagaimana saya menulis definisi jika AE adalah kelas template AE <class T> dan c7 bukan int tetapi T :: size_type? Saya memiliki nilai yang diinisialisasi ke "-1" di header tetapi dentang mengatakan nilai tidak ditentukan dan saya tidak tahu cara menulis definisi.
Fabian
@Fabian Saya sedang bepergian dan menggunakan telepon dan agak sibuk ... tetapi menurut saya komentar Anda terdengar seperti itu paling baik ditulis sebagai pertanyaan baru. Tulis MCVE termasuk kesalahan yang Anda dapatkan, juga mungkin masukkan apa yang dikatakan gcc. Saya yakin orang akan memberi tahu Anda dengan cepat apa itu.
HostileFork mengatakan jangan percaya SE
@HostileFork: Saat menulis MCVE, terkadang Anda menemukan solusinya sendiri. Untuk kasus saya jawabannya adalah template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;dimana KeyContainer adalah typedef dari std :: vector <K>. Seseorang harus membuat daftar semua parameter template dan menulis nama jenis karena itu adalah jenis yang bergantung. Mungkin seseorang akan menganggap komentar ini berguna. Namun, sekarang saya bertanya-tanya bagaimana cara mengekspor ini dalam DLL karena kelas template tentu saja di header. Apakah saya perlu mengekspor c7 ???
Fabian
24

Cara lain untuk melakukan ini, untuk tipe integer, adalah dengan mendefinisikan konstanta sebagai enum di kelas:

class test
{
public:
    enum { N = 10 };
};
Stephen Chu
sumber
2
Dan ini mungkin akan menyelesaikan masalah. Ketika N digunakan sebagai parameter untuk min () itu akan menyebabkan sementara dibuat daripada mencoba merujuk ke variabel yang seharusnya ada.
Edward Strange
Ini memiliki keuntungan karena dapat dijadikan pribadi.
Agostino
11

Bukan hanya int. Tetapi Anda tidak dapat menentukan nilai dalam deklarasi kelas. Jika Anda memiliki:

class classname
{
    public:
       static int const N;
}

di file .h maka Anda harus memiliki:

int const classname::N = 10;

di file .cpp.

Amardeep AC9MF
sumber
2
Saya sadar bahwa Anda dapat mendeklarasikan variabel jenis apa pun di dalam deklarasi kelas. Saya mengatakan bahwa saya pikir konstanta integer statis juga dapat didefinisikan di dalam deklarasi kelas. Bukankah ini masalahnya? Jika tidak, mengapa kompilator tidak memberikan kesalahan pada baris di mana saya mencoba untuk mendefinisikannya di dalam kelas? Selain itu, mengapa baris std :: cout tidak menyebabkan kesalahan linker, tetapi baris std :: min menyebabkannya?
Komandan Tinggi4
Tidak, tidak dapat mendefinisikan anggota statis dalam deklarasi kelas karena inisialisasi mengeluarkan kode. Tidak seperti fungsi sebaris yang juga memancarkan kode, definisi statis bersifat unik secara global.
Amardeep AC9MF
@ HighCommander4: Anda dapat menyediakan penginisialisasi untuk static constanggota integral dalam definisi kelas. Tapi itu tetap tidak mendefinisikan anggota itu. Lihat jawaban Noah Roberts untuk detailnya.
AnT
9

Berikut cara lain untuk mengatasi masalah tersebut:

std::min(9, int(test::N));

(Saya pikir jawaban Crazy Eddie dengan tepat menjelaskan mengapa masalah itu ada.)

karadoc.dll
sumber
5
atau bahkanstd::min(9, +test::N);
Tomilov Anatoliy
Inilah pertanyaan besarnya: apakah semua ini optimal? Saya tidak tahu tentang kalian, tetapi ketertarikan besar saya untuk melewatkan definisi adalah bahwa itu tidak boleh memakan memori dan tidak ada overhead dalam menggunakan const statis.
Opux
6

Mulai C ++ 11 Anda dapat menggunakan:

static constexpr int N = 10;

Ini secara teoritis masih mengharuskan Anda untuk mendefinisikan konstanta dalam file .cpp, tetapi selama Anda tidak mengambil alamatnya N, sangat tidak mungkin implementasi compiler apa pun akan menghasilkan kesalahan;).

Carlo Wood
sumber
Dan bagaimana jika Anda perlu meneruskan nilai sebagai argumen tipe 'const int &' seperti dalam contoh? :-)
Wormer
Itu bekerja dengan baik. Anda tidak membuat instance N dengan cara itu, hanya meneruskan referensi const ke sementara. wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood
C ++ 17 mungkin, bukan C ++ 14, dan bahkan bukan C ++ 17 di versi gcc 6.3.0 sebelumnya dan yang lebih rendah, ini bukan hal standar. Tapi terima kasih telah menyebutkan ini.
Wormer
Ah ya, kamu benar. Saya tidak mencoba c ++ 14 di wandbox. Baiklah, itu adalah bagian di mana saya berkata "Ini secara teoritis masih mengharuskan Anda untuk mendefinisikan konstanta". Jadi, Anda benar bahwa itu bukan 'standar'.
Carlo Wood
3

C ++ memungkinkan anggota konstanta statis untuk didefinisikan di dalam kelas

Tidak, 3.1 §2 mengatakan:

Deklarasi adalah definisi kecuali jika ia mendeklarasikan fungsi tanpa menentukan badan fungsi (8.4), ia berisi penentu eksternal (7.1.1) atau spesifikasi-link (7.5) dan bukan penginisialisasi maupun functionbody, ia mendeklarasikan data statis anggota dalam definisi kelas (9.4), itu adalah deklarasi nama kelas (9.1), itu adalah deklarasi buram-enum (7.2), atau itu adalah deklarasi typedef (7.1.3), deklarasi menggunakan (7.3. 3), atau petunjuk penggunaan (7.3.4).

fredoverflow
sumber