String konstan statis (anggota kelas)

444

Saya ingin memiliki konstanta statis pribadi untuk kelas (dalam hal ini bentuk-pabrik).

Saya ingin memiliki sesuatu seperti itu.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

Sayangnya saya mendapatkan semua jenis kesalahan dari kompiler C ++ (g ++), seperti:

ISO C ++ melarang inisialisasi anggota 'RECTANGLE'

inisialisasi in-class yang tidak valid dari anggota data statis tipe non-integral 'std :: string'

kesalahan: membuat 'RECTANGLE' statis

Ini memberitahu saya bahwa desain anggota semacam ini tidak sesuai dengan standar. Bagaimana Anda memiliki konstanta literal pribadi (atau mungkin publik) tanpa harus menggunakan arahan #define (saya ingin menghindari keburukan data global!)

Bantuan apa pun dihargai.

lb.
sumber
15
Terima kasih atas semua jawaban Anda! Hidup begitu lama!
£
Dapatkah seseorang tolong beri tahu saya apa tipe 'integral' itu? Terima kasih banyak.
£
1
Tipe integral mengacu pada tipe yang mewakili angka integer. Lihat publib.boulder.ibm.com/infocenter/comphelp/v8v101/…
bleater
String statis privat di pabrik Anda bukan solusi yang baik - pertimbangkan bahwa klien pabrik Anda harus mengetahui bentuk apa yang didukung, jadi alih-alih menyimpannya di static privat, masukkan mereka ke namespace statis sebagai const statis std :: string RECTANGLE = "Rectangle ".
LukeCodeBaker
jika kelas Anda adalah kelas templat maka lihat stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Jawaban:

469

Anda harus mendefinisikan anggota statis Anda di luar definisi kelas dan menyediakan inisialisasi di sana.

Pertama

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

lalu

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

Sintaks yang awalnya Anda coba gunakan (initializer di dalam definisi kelas) hanya diperbolehkan dengan tipe integral dan enum.


Mulai dari C ++ 17 Anda memiliki opsi lain, yang sangat mirip dengan deklarasi asli Anda: variabel inline

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

Tidak diperlukan definisi tambahan.

Atau alih-alih constAnda dapat mendeklarasikannya constexprdalam varian ini. Eksplisit inlinetidak lagi diperlukan, karena constexprtersirat inline.

Semut
sumber
8
Juga, jika tidak ada persyaratan untuk menggunakan string STL, Anda mungkin juga hanya mendefinisikan karakter const *. (less overhead)
KSchmidt
50
Saya tidak yakin selalu lebih murah - tergantung penggunaan. Jika anggota ini dimaksudkan untuk diteruskan sebagai argumen ke fungsi yang mengambil const string &, akan ada sementara dibuat untuk setiap panggilan vs satu objek penciptaan string selama inisialisasi. Overhead IMHO untuk membuat objek string statis diabaikan.
Tadeusz Kopec
23
Saya lebih suka menggunakan std :: string sepanjang waktu juga. Biaya overhead dapat diabaikan, tetapi Anda memiliki lebih banyak opsi dan jauh lebih kecil kemungkinannya untuk menulis hal-hal bodoh seperti "sihir" == A :: RECTANGLE hanya untuk membandingkan alamat mereka ...
Matthieu M.
9
yang char const*memiliki kebaikan bahwa itu diinisialisasi sebelum semua inisialisasi dinamis dilakukan. Jadi dalam konstruktor objek apa pun, Anda dapat mengandalkan RECTANGLEtelah diinisialisasi sebelumnya.
Johannes Schaub - litb
8
@cirosantilli: Karena sejak awal kali dalam inisialisasi C ++ adalah bagian dari definisi , bukan deklarasi . Dan deklarasi anggota data di dalam kelas hanya itu: deklarasi. (Di sisi lain, pengecualian dibuat untuk konstanta integral dan anggota enum, dan dalam C ++ 11 - untuk konstanta anggota tipe literal .)
AnT
153

Di C ++ 11 Anda dapat melakukannya sekarang:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};
jurang.7
sumber
30
Sayangnya solusi ini tidak berfungsi untuk std :: string.
HelloWorld
2
Perhatikan bahwa 1. ini hanya berfungsi dengan literal dan 2. ini tidak sesuai standar, meskipun Gnu / GCC mengkompensasi denda, kompiler lain akan melempar kesalahan. Definisi harus ada dalam tubuh.
ManuelSchneid3r
2
@ ManuelSchneid3r Bagaimana sebenarnya ini "tidak sesuai standar"? Sepertinya inisialisasi bog-standard C ++ 11 brace-atau-sama dengan saya.
underscore_d
3
@ rvighne, tidak, itu tidak benar. constexprmenyiratkan constuntuk var, bukan untuk mengetikkan poin. Yaitu static constexpr const char* constsama dengan static constexpr const char*, tetapi tidak sama dengan static constexpr char*.
midenok
2
@ abyss.7 - Terima kasih atas jawaban Anda, dan saya punya satu lagi tolong: Mengapa itu harus statis?
Guy Avraham
34

Di dalam definisi kelas Anda hanya dapat mendeklarasikan anggota statis. Mereka harus didefinisikan di luar kelas. Untuk konstanta integral kompilasi waktu, standar membuat pengecualian bahwa Anda dapat "menginisialisasi" anggota. Itu masih bukan definisi. Mengambil alamat tidak akan berfungsi tanpa definisi, misalnya.

Saya ingin menyebutkan bahwa saya tidak melihat manfaat dari menggunakan std :: string over const char [] untuk konstanta . std :: string bagus dan semua tetapi membutuhkan inisialisasi dinamis. Jadi, jika Anda menulis sesuatu seperti

const std::string foo = "hello";

pada lingkup namespace, konstruktor foo akan dijalankan tepat sebelum eksekusi mulai utama dan konstruktor ini akan membuat salinan konstanta "halo" di memori tumpukan. Kecuali Anda benar-benar membutuhkan RECTANGLE untuk menjadi std :: string yang Anda bisa tulis

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

Sana! Tidak ada alokasi tumpukan, tidak ada penyalinan, tidak ada inisialisasi dinamis.

Cheers, s.

sellibitze
sumber
1
Ini adalah jawaban pra C ++ 11. Gunakan standar C ++ dan gunakan std :: string_view.
1
C ++ 11 tidak memiliki std :: string_view.
Lukas Salich
17

Ini hanya informasi tambahan, tetapi jika Anda benar-benar menginginkan string dalam file header, coba sesuatu seperti:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Meskipun aku ragu itu direkomendasikan.

GManNickG
sumber
Itu terlihat keren :) - saya kira Anda memiliki latar belakang dalam bahasa lain selain c ++?
£
5
Saya tidak akan merekomendasikan itu. Saya sering melakukan ini. Ini berfungsi dengan baik dan saya merasa lebih jelas daripada meletakkan string di file implementasi. Data aktual dari std :: string masih terletak di heap. Saya akan mengembalikan const char *, dalam hal ini Anda tidak perlu mendeklarasikan variabel statis sehingga deklarasi akan mengambil lebih sedikit ruang (kode bijaksana). Hanya masalah selera.
Zoomulator
15

Di C ++ 17 Anda dapat menggunakan variabel sebaris :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Perhatikan bahwa ini berbeda dari jawaban yang buruk.7 : Yang ini mendefinisikan std::stringobjek aktual , bukan aconst char*

Oz Solomon
sumber
Tidakkah Anda pikir menggunakan inlineakan membuat banyak duplikat?
shuva
1
@shuva Tidak, variabel tidak akan diduplikasi .
zett42
8

Untuk menggunakan sintaks inisialisasi di dalam kelas, konstanta harus berupa konstanta statis integral atau tipe enumerasi yang diinisialisasi dengan ekspresi konstan.

Ini batasannya. Karenanya, dalam hal ini Anda perlu mendefinisikan variabel di luar kelas. lihat jawaban dari @AndreyT

aJ.
sumber
7

Variabel statis kelas dapat dideklarasikan di header tetapi harus didefinisikan dalam file .cpp. Ini karena hanya ada satu contoh variabel statis dan kompilator tidak dapat memutuskan di mana file objek yang dihasilkan untuk meletakkannya sehingga Anda harus membuat keputusan.

Untuk menjaga definisi nilai statis dengan deklarasi di C ++ 11 struktur statis bersarang dapat digunakan. Dalam hal ini anggota statis adalah struktur dan harus didefinisikan dalam file .cpp, tetapi nilainya ada di header.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

Alih-alih menginisialisasi anggota individu seluruh struktur statis diinisialisasi dalam .cpp:

A::_Shapes A::shape;

Nilai diakses dengan

A::shape.RECTANGLE;

atau - karena anggotanya pribadi dan dimaksudkan untuk digunakan hanya dari A - with

shape.RECTANGLE;

Perhatikan bahwa solusi ini masih menderita dari masalah urutan inisialisasi variabel statis. Ketika nilai statis digunakan untuk menginisialisasi variabel statis lain, yang pertama mungkin belum diinisialisasi.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

Dalam hal ini header variabel statis akan berisi {""} atau {".h", ".hpp"}, tergantung pada urutan inisialisasi yang dibuat oleh penghubung.

Seperti disebutkan oleh @ abyss.7 Anda juga dapat menggunakan constexprjika nilai variabel dapat dihitung pada waktu kompilasi. Tetapi jika Anda mendeklarasikan string Anda static constexpr const char*dan program Anda menggunakan std::stringsebaliknya, akan ada overhead karena std::stringobjek baru akan dibuat setiap kali Anda menggunakan konstanta seperti itu:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}
Marko Mahnič
sumber
Jawaban siap Marko. Dua detail: satu tidak memerlukan file cpp untuk anggota kelas statis, dan juga silakan gunakan std :: string_view untuk segala jenis konstanta.
4

Standar saat ini hanya memungkinkan inisialisasi tersebut untuk jenis integral konstan statis. Jadi, Anda perlu melakukan seperti yang dijelaskan AndreyT. Namun, itu akan tersedia dalam standar berikutnya melalui sintaks inisialisasi anggota baru .

Leandro TC Melo
sumber
4

mungkin lakukan saja:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

atau

#define RECTANGLE "rectangle"
chikuba
sumber
11
Menggunakan #define ketika konstanta yang diketik dapat digunakan adalah salah.
Artur Czajka
Contoh pertama Anda pada dasarnya adalah solusi yang baik jika Anda tidak memiliki constexprtetapi Anda tidak dapat membuat fungsi statis const.
Frank Puffer
Solusi ini harus dihindari. Itu menciptakan string baru di setiap doa. Ini akan lebih baik:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon
Mengapa menggunakan kontainer full-blown sebagai nilai pengembalian? Gunakan std :: string_vew .. isinya akan tetap valid dalam kasus ini. bahkan lebih baik menggunakan string literal untuk membuat dan mengembalikan tampilan string ... dan yang terakhir tetapi tidak kalah pentingnya, nilai return const tidak memiliki arti atau efek di sini .. ya, dan memiliki ini sebagai inline, bukan statis, di beberapa header di bernama namespace ... dan tolong jadikannya constexpr
4

Anda bisa menggunakan const char*solusi yang disebutkan di atas, tetapi jika Anda membutuhkan string setiap saat, Anda akan memiliki banyak overhead.
Di sisi lain, string statis membutuhkan inisialisasi dinamis, jadi jika Anda ingin menggunakan nilainya selama inisialisasi variabel global / statis lain, Anda mungkin mengalami masalah urutan inisialisasi. Untuk menghindari itu, hal termurah adalah mengakses objek string statis melalui pengambil, yang memeriksa apakah objek Anda diinisialisasi atau tidak.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

Ingatlah untuk hanya menggunakan A::getS(). Karena setiap threading hanya dapat dimulai dengan main(), dan A_s_initializeddiinisialisasi sebelumnya main(), Anda tidak perlu kunci bahkan dalam lingkungan multithreaded A_s_initializedadalah 0 secara default (sebelum inisialisasi dinamis), jadi jika Anda menggunakan getS()sebelum s diinisialisasi, Anda memanggil fungsi init dengan aman.

Btw, dalam jawaban di atas: " static const std :: string RECTANGLE () const ", fungsi statis tidak bisa constkarena mereka tidak dapat mengubah keadaan jika objek tetap (tidak ada pointer ini).

pengguna2806882
sumber
4

Maju cepat ke 2018 dan C ++ 17.

  • jangan gunakan std :: string, gunakan std :: string_view literals
  • mohon perhatikan 'constexpr' di bawah. Ini juga merupakan mekanisme "waktu kompilasi".
  • tidak ada sebaris tidak berarti pengulangan
  • tidak ada file cpp tidak diperlukan untuk ini
  • static_assert 'works' hanya pada waktu kompilasi

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

Di atas adalah standar warga negara C ++ yang tepat dan sah. Itu bisa dengan mudah terlibat dalam setiap dan semua std :: algoritma, wadah, utilitas dan semacamnya. Sebagai contoh:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

Nikmati C ++ standar


sumber
Gunakan std::string_viewuntuk konstanta hanya jika Anda menggunakan string_viewparameter di semua fungsi Anda. Jika salah satu fungsi Anda menggunakan const std::string&parameter, salinan string akan dibuat ketika Anda melewati string_viewkonstanta melalui parameter itu. Jika konstanta Anda bertipe std::stringsalinan tidak akan dibuat baik untuk const std::string&parameter maupun untuk std::string_viewparameter.
Marko Mahnič
Jawaban yang bagus, tetapi ingin tahu mengapa string_view dikembalikan dari suatu fungsi? Trik semacam ini berguna sebelum inlinevariabel tiba di C ++ 17 dengan semantik ODR mereka. Tapi string_view adalah C ++ 17 juga, jadi hanya constexpr auto some_str = "compile time"sv;melakukan pekerjaan (dan sebenarnya, itu bukan variabel, itu constexpr, jadi inlineimplisit; jika Anda memiliki variabel - yaitu tidak constexpr- maka inline auto some_str = "compile time"sv;akan melakukannya, meskipun tentu saja namespace-scope variabel, yang pada dasarnya adalah variabel global, jarang merupakan ide yang bagus).
Kehilangan Mentalitas