Bagaimana Anda membuat kelas statis di C ++?


264

Bagaimana Anda membuat kelas statis di C ++? Saya harus dapat melakukan sesuatu seperti:

cout << "bit 5 is " << BitParser::getBitAt(buffer, 5) << endl;

Dengan asumsi saya membuat BitParserkelas. Seperti apa BitParserdefinisi kelas itu?


198
Ya. Kembali di masa lalu, kami baru saja menyebutnya "fungsi". Anda anak-anak hari ini dengan "ruang nama" Anda yang gila. Hei, turun dari halaman saya! <getar kepalan>
Vagrant

7
@ Jelas fungsi di dalam namespace masih merupakan fungsi. Fungsi yang termasuk kelas disebut metode. Jika ini adalah metode statis, Anda menjalankannya dengan cara yang sama seolah-olah itu adalah fungsi di dalam namespace.

48
5 tahun kemudian dan sekarang saya memihak @Vagrant. Pertanyaan yang konyol!
andrewrk

16
9 tahun kemudian dan sekarang saya memiliki bahasa pemrograman sendiri yang bahkan tidak memiliki kelas: ziglang.org
andrewrk

1
Kelas mirip wadah IMO (yang hanya memiliki metode statis) berguna dalam kasus-kasus tertentu.
AarCee

Jawaban:


272

Jika Anda mencari cara untuk menerapkan kata kunci "statis" ke suatu kelas, seperti yang Anda dapat di C # misalnya, maka Anda tidak akan dapat melakukannya tanpa menggunakan C ++ yang Dikelola.

Tapi sepertinya sampel Anda, Anda hanya perlu membuat metode statis publik pada objek BitParser Anda. Seperti itu:

BitParser.h

class BitParser
{
 public:
  static bool getBitAt(int buffer, int bitIndex);

  // ...lots of great stuff

 private:
  // Disallow creating an instance of this object
  BitParser() {}
};

BitParser.cpp

bool BitParser::getBitAt(int buffer, int bitIndex)
{
  bool isBitSet = false;
  // .. determine if bit is set
  return isBitSet;
}

Anda dapat menggunakan kode ini untuk memanggil metode dengan cara yang sama seperti kode contoh Anda.

Semoga itu bisa membantu! Bersulang.


2
OJ, Anda memiliki kesalahan sintaksis . Kata kunci statis hanya boleh digunakan dalam definisi kelas, dan bukan dalam definisi metode.
andrewrk

90
Untuk memperjelas niat Anda dalam pendekatan ini, Anda juga dapat menggunakan konstruktor pribadi. private: BitParser() {}Ini akan mencegah siapa pun membuat instance.
Danvil

7
@MoatazElmasry safety thread merupakan masalah saat Anda berbagi status. Dalam implementasi di atas tidak ada status yang dibagikan, sehingga tidak ada masalah dengan keamanan utas ... kecuali Anda cukup bodoh untuk menggunakan statika di dalam fungsi-fungsi itu. Jadi ya, kode di atas aman untuk thread, jauhkan status gigih dari fungsi Anda dan Anda baik-baik saja.
OJ.

@MoatazElmasry Salah. Dua utas tidak dapat mengubah variabel lokal non-statis dalam fungsi statis.
OJ.

12
Jika C ++ 11, saya berpendapat lebih BitParser() = delete;baik menyampaikan maksud untuk menghapus konstruktor dengan benar (tidak hanya menyembunyikannya private).
phoenix

247

Pertimbangkan solusi Matt Price .

  1. Dalam C ++, "kelas statis" tidak memiliki arti. Yang terdekat adalah kelas dengan hanya metode dan anggota statis.
  2. Menggunakan metode statis hanya akan membatasi Anda.

Apa yang Anda inginkan adalah, dinyatakan dalam semantik C ++, untuk menempatkan fungsi Anda (karena itu adalah fungsi) dalam namespace.

Edit 2011-11-11

Tidak ada "kelas statis" di C ++. Konsep terdekat adalah kelas dengan hanya metode statis. Sebagai contoh:

// header
class MyClass
{
   public :
      static void myMethod() ;
} ;

// source
void MyClass::myMethod()
{
   // etc.
}

Tetapi Anda harus ingat bahwa "kelas statis" adalah retasan dalam jenis bahasa seperti Java (misalnya C #) yang tidak dapat memiliki fungsi non-anggota, jadi mereka harus memindahkannya di dalam kelas sebagai metode statis.

Di C ++, yang Anda inginkan adalah fungsi non-anggota yang akan Anda deklarasikan di namespace:

// header
namespace MyNamespace
{
   void myMethod() ;
}

// source
namespace MyNamespace
{
   void myMethod()
   {
      // etc.
   }
}

Mengapa demikian?

Di C ++, namespace lebih kuat daripada kelas untuk pola "Java static method", karena:

  • metode statis memiliki akses ke simbol pribadi kelas
  • metode statis pribadi masih terlihat (jika tidak dapat diakses) untuk semua orang, yang melanggar enkapsulasi
  • metode statis tidak dapat dideklarasikan ke depan
  • metode statis tidak dapat kelebihan beban oleh pengguna kelas tanpa memodifikasi header perpustakaan
  • tidak ada yang dapat dilakukan dengan metode statis yang tidak dapat dilakukan lebih baik daripada fungsi non-anggota (mungkin teman) di namespace yang sama
  • ruang nama memiliki semantik sendiri (mereka dapat digabungkan, mereka bisa anonim, dll.)
  • dll.

Kesimpulan: Jangan menyalin / menempelkan pola Java / C # di C ++. Dalam Java / C #, polanya wajib. Tetapi dalam C ++, itu adalah gaya yang buruk.

Edit 2010-06-10

Ada argumen yang mendukung metode statis karena kadang-kadang, kita perlu menggunakan variabel anggota pribadi statis.

Saya agak tidak setuju, seperti yang ditunjukkan di bawah ini:

Solusi "Anggota pribadi statis"

// HPP

class Foo
{
   public :
      void barA() ;
   private :
      void barB() ;
      static std::string myGlobal ;
} ;

Pertama, myGlobal disebut myGlobal karena masih merupakan variabel pribadi global. Melihat sumber CPP akan menjelaskan bahwa:

// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP

void Foo::barA()
{
   // I can access Foo::myGlobal
}

void Foo::barB()
{
   // I can access Foo::myGlobal, too
}

void barC()
{
   // I CAN'T access Foo::myGlobal !!!
}

Pada pandangan pertama, fakta bahwa fungsi barC gratis tidak dapat mengakses Foo :: myGlobal tampaknya merupakan hal yang baik dari sudut pandang enkapsulasi ... Ini keren karena seseorang yang melihat HPP tidak akan dapat (kecuali menggunakan sabotase) untuk mengakses Foo :: myGlobal.

Tetapi jika Anda melihatnya dengan seksama, Anda akan menemukan bahwa itu adalah kesalahan kolosal: Tidak hanya variabel pribadi Anda masih harus dinyatakan dalam HPP (dan karenanya, terlihat oleh seluruh dunia, meskipun bersifat pribadi), tetapi Anda harus mendeklarasikan dalam HPP yang sama semua (seperti dalam SEMUA) fungsi yang akan diizinkan untuk mengaksesnya !!!

Jadi menggunakan anggota statis pribadi seperti berjalan di luar telanjang dengan daftar kekasih Anda bertato di kulit Anda: Tidak ada yang diizinkan untuk menyentuh, tetapi semua orang dapat mengintip. Dan bonusnya: Setiap orang dapat memiliki nama-nama yang berwenang bermain dengan hadiah Anda.

private memang ... :-D

Solusi "Ruang nama anonim"

Ruang nama anonim akan memiliki keuntungan membuat hal-hal pribadi menjadi sangat pribadi.

Pertama, tajuk HPP

// HPP

namespace Foo
{
   void barA() ;
}

Hanya untuk memastikan Anda berkomentar: Tidak ada deklarasi barB atau myGlobal yang tidak berguna. Yang berarti bahwa tidak ada yang membaca tajuk yang tahu apa yang tersembunyi di balik barA.

Kemudian, CPP:

// CPP
namespace Foo
{
   namespace
   {
      std::string myGlobal ;

      void Foo::barB()
      {
         // I can access Foo::myGlobal
      }
   }

   void barA()
   {
      // I can access myGlobal, too
   }
}

void barC()
{
   // I STILL CAN'T access myGlobal !!!
}

Seperti yang Anda lihat, seperti deklarasi "kelas statis", fooA dan fooB masih dapat mengakses myGlobal. Tapi tidak ada orang lain yang bisa. Dan tidak ada orang lain di luar CPP ini yang tahu fooB dan myGlobal bahkan ada!

Tidak seperti "kelas statis" yang berjalan telanjang dengan buku alamatnya bertato di kulitnya, namespace "anonim" sepenuhnya berpakaian , yang tampaknya AFAIK dienkapsulasi lebih baik.

Apakah itu penting?

Kecuali pengguna kode Anda penyabot (Aku akan membiarkan Anda, sebagai latihan, menemukan bagaimana seseorang dapat mengakses ke bagian pribadi dari public class menggunakan kotor hack perilaku-terdefinisi ...), apa privateyang private, bahkan jika itu terlihat di privatebagian kelas yang dinyatakan dalam header.

Namun, jika Anda perlu menambahkan "fungsi pribadi" lain dengan akses ke anggota pribadi, Anda masih harus mendeklarasikannya ke seluruh dunia dengan memodifikasi header, yang merupakan paradoks sejauh yang saya ketahui: Jika saya mengubah implementasi kode saya (bagian CPP), maka antarmuka (bagian HPP) TIDAK boleh berubah. Mengutip Leonidas: " Ini ENCAPSULASI! "

Edit 2014-09-20

Kapan kelas metode statis sebenarnya lebih baik daripada ruang nama dengan fungsi non-anggota?

Saat Anda perlu mengelompokkan fungsi-fungsi dan memberi makan grup itu ke templat:

namespace alpha
{
   void foo() ;
   void bar() ;
}

struct Beta
{
   static void foo() ;
   static void bar() ;
};

template <typename T>
struct Gamma
{
   void foobar()
   {
      T::foo() ;
      T::bar() ;
   }
};

Gamma<alpha> ga ; // compilation error
Gamma<Beta> gb ;  // ok
gb.foobar() ;     // ok !!!

Karena, jika kelas bisa menjadi parameter templat, ruang nama tidak bisa.


3
GCC mendukung -fno-access-control, yang dapat digunakan dalam tes unit whitebox untuk mengakses anggota kelas pribadi. Itu tentang satu-satunya alasan saya bisa memikirkan untuk membenarkan menggunakan anggota kelas daripada global anonim / statis dalam implementasi.
Tom

8
@ Tom: Solusi lintas platform adalah dengan menambahkan kode berikut #define private publicdi header ... ^ _ ^ ...
paercebal

1
@ Tom: ngomong-ngomong, IMHO, bahkan mempertimbangkan pengujian unit, kontra "menunjukkan terlalu banyak barang" melebihi pro. Saya kira solusi alternatif adalah dengan meletakkan kode yang akan diuji dalam fungsi mengambil parameter yang diperlukan (dan tidak lebih) di utilitiesnamespace. Dengan cara ini, fungsi ini dapat diuji unit, dan masih tidak memiliki akses khusus ke anggota pribadi (seperti yang diberikan sebagai parameter pada panggilan fungsi) ...
paercebal

@ otak saya akan melompat ke atas kapal Anda, tapi saya punya satu pemesanan terakhir. Jika seseorang melompat pada Anda, namespacemereka tidak akan mendapatkan akses ke anggota Anda global, meskipun disembunyikan,? Jelas mereka harus menebak, tetapi kecuali Anda sengaja mengaburkan kode Anda, nama variabel cukup mudah ditebak.
Zak

@ Zak: Memang, mereka bisa, tetapi hanya dengan mencoba melakukannya di file CPP di mana variabel myGlobal dideklarasikan. Intinya lebih visibilitas daripada aksesibilitas. Di kelas statis, variabel myGlobal bersifat pribadi, tetapi masih terlihat. Ini tidak sepenting kelihatannya, tetapi masih, dalam DLL, menunjukkan simbol yang harus pribadi untuk DLL dalam header yang diekspor dapat menjadi canggung ... Di namespace, myGlobal hanya ada di file CPP (Anda bahkan bisa lebih jauh dan membuatnya statis). Variabel itu tidak muncul di header publik.
paercebal

63

Anda juga dapat membuat fungsi gratis di namespace:

Di BitParser.h

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex);
}

Di BitParser.cpp

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex)
    {
        //get the bit :)
    }
}

Secara umum ini akan menjadi cara yang disukai untuk menulis kode. Ketika tidak perlu untuk objek tidak menggunakan kelas.


1
Dalam beberapa kasus Anda mungkin ingin memiliki enkapsulasi data bahkan jika kelasnya sebagian besar "statis". Anggota kelas privat statis akan memberikan ini kepada Anda. Anggota Namespace selalu bersifat publik dan tidak dapat memberikan enkapsulasi data.
Torleif

Jika "anggota" var hanya dideklarasikan dan diakses dari file .cpp, itu lebih pribadi daripada var pribadi yang dinyatakan dalam file .h. BUKAN bahwa saya merekomendasikan teknik ini.
jmucchiello

3
@ Torleif: Anda salah. ruang nama lebih baik untuk enkapsulasi daripada anggota pribadi statis. Lihat jawaban saya untuk demonstrasi.
paercebal

1
ya tetapi di namespace Anda harus menjaga urutan fungsi, berbeda dengan kelas dengan anggota statis, misalnya void a () {b ();} b () {} akan menghasilkan kesalahan dalam namespace tetapi tidak di kelas dengan anggota statis
Moataz Elmasry

13

Jika Anda mencari cara untuk menerapkan kata kunci "statis" ke suatu kelas, seperti Anda dapat menggunakan C # misalnya

kelas statis hanyalah kompiler yang memegang Anda dan menghentikan Anda dari menulis metode / variabel instan.

Jika Anda hanya menulis kelas normal tanpa metode instance / variabel, itu adalah hal yang sama, dan ini adalah apa yang akan Anda lakukan di C ++


Bukan untuk mengeluh (terutama pada Anda), tetapi beberapa pegangan tangan penyusun untuk menjaga saya dari menulis atau memotong / menempel kata static200 kali akan menjadi hal yang baik.
3Dave

Setuju - tetapi kelas statis di C # tidak melakukan ini juga. Itu hanya gagal untuk mengkompilasi ketika Anda lupa menempelkan statis di sana :-)
Orion Edwards

Ya - cukup adil. Makro saya ditampilkan. Jujur, jika saya mendeklarasikan kelas sebagai statis, kompiler hanya akan membuang kesalahan jika saya mencoba untuk instantiate. Aturan yang mengharuskan saya mengulangi diri saya sendiri adalah menjengkelkan dan harus menjadi yang pertama melawan tembok ketika revolusi datang.
3Dave

11

Di C ++ Anda ingin membuat fungsi statis suatu kelas (bukan kelas statis).

class BitParser {
public:
  ...
  static ... getBitAt(...) {
  }
};

Anda kemudian dapat memanggil fungsi menggunakan BitParser :: getBitAt () tanpa membuat instance objek yang saya anggap sebagai hasil yang diinginkan.


11

Bisakah saya menulis sesuatu seperti static class?

Tidak , menurut draft standar C ++ 11 N3337, Annex C 7.1.1:

Ubah: Dalam C ++, penentu statis atau eksternal hanya dapat diterapkan pada nama objek atau fungsi. Menggunakan specifier ini dengan deklarasi tipe adalah ilegal di C ++. Dalam C, penentu ini diabaikan ketika digunakan pada deklarasi tipe. Contoh:

static struct S {    // valid C, invalid in C++
  int i;
};

Dasar Pemikiran: Penentu kelas penyimpanan tidak memiliki arti apa pun saat dikaitkan dengan suatu jenis. Dalam C ++, anggota kelas dapat dideklarasikan dengan specifier kelas penyimpanan statis. Mengizinkan specifier kelas penyimpanan pada deklarasi tipe dapat membuat kode membingungkan bagi pengguna.

Dan suka struct, classjuga merupakan jenis deklarasi.

Hal yang sama dapat disimpulkan dengan berjalan di pohon sintaks di Lampiran A.

Sangat menarik untuk dicatat bahwa static structitu legal di C, tetapi tidak berpengaruh: Mengapa dan kapan menggunakan struktur statis dalam pemrograman C?


6

Seperti yang telah dicatat di sini, cara yang lebih baik untuk mencapai ini di C ++ mungkin menggunakan ruang nama. Tetapi karena tidak ada yang menyebutkan finalkata kunci di sini, saya memposting seperti apa persamaan langsung static classdari C # di C ++ 11 atau lebih baru:

class BitParser final
{
public:
  BitParser() = delete;

  static bool GetBitAt(int buffer, int pos);
};

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}

5

Anda 'dapat' memiliki kelas statis di C ++, seperti yang disebutkan sebelumnya, kelas statis adalah salah satu yang tidak memiliki objek yang dipakai. Dalam C ++, ini dapat diperoleh dengan mendeklarasikan konstruktor / destruktor sebagai pribadi. Hasil akhirnya sama.


Apa yang Anda sarankan dapat membuat Kelas tunggal, tetapi tidak sama dengan kelas statis.
ksinkar

4

Di Dikelola C ++, sintaks kelas statis adalah: -

public ref class BitParser abstract sealed
{
    public:
        static bool GetBitAt(...)
        {
            ...
        }
}

... lebih baik terlambat daripada tidak sama sekali...


3

Ini mirip dengan cara C # melakukannya di C ++

Dalam C # file.cs Anda dapat memiliki var pribadi di dalam fungsi publik. Ketika di file lain Anda dapat menggunakannya dengan memanggil namespace dengan fungsi seperti pada:

MyNamespace.Function(blah);

Berikut cara menerapkan hal yang sama di C ++:

SharedModule.h

class TheDataToBeHidden
{
  public:
    static int _var1;
    static int _var2;
};

namespace SharedData
{
  void SetError(const char *Message, const char *Title);
  void DisplayError(void);
}

SharedModule.cpp

//Init the data (Link error if not done)
int TheDataToBeHidden::_var1 = 0;
int TheDataToBeHidden::_var2 = 0;


//Implement the namespace
namespace SharedData
{
  void SetError(const char *Message, const char *Title)
  {
    //blah using TheDataToBeHidden::_var1, etc
  }

  void DisplayError(void)
  {
    //blah
  }
}

OtherFile.h

#include "SharedModule.h"

OtherFile.cpp

//Call the functions using the hidden variables
SharedData::SetError("Hello", "World");
SharedData::DisplayError();

2
Tapi semua orang cango ke TheDataToBeHidden -> Ini bukan solusi
Guy L

3

Tidak seperti bahasa pemrograman lain yang dikelola, "kelas statis" tidak memiliki arti dalam C ++. Anda dapat menggunakan fungsi anggota statis.


0

Satu kasus di mana ruang nama mungkin tidak begitu berguna untuk mencapai "kelas statis" adalah ketika menggunakan kelas-kelas ini untuk mencapai komposisi di atas warisan. Ruang nama tidak boleh menjadi teman kelas dan karenanya tidak dapat mengakses anggota pribadi kelas.

class Class {
 public:
  void foo() { Static::bar(*this); }    

 private:
  int member{0};
  friend class Static;
};    

class Static {
 public:
  template <typename T>
  static void bar(T& t) {
    t.member = 1;
  }
};

0

Salah satu (dari banyak) alternatif, tetapi yang paling (menurut saya) anggun (dibandingkan dengan menggunakan ruang nama dan konstruktor pribadi untuk meniru perilaku statis), cara untuk mencapai perilaku "kelas yang tidak dapat dipakai" dalam C ++ adalah dengan mendeklarasikan fungsi virtual murni dummy dengan privatepengubah akses.

class Foo {
   public:
     static int someMethod(int someArg);

   private:
     virtual void __dummy() = 0;
};

Jika Anda menggunakan C ++ 11, Anda bisa bekerja lebih keras untuk memastikan bahwa kelas tidak diwarisi (untuk murni meniru perilaku kelas statis) dengan menggunakan finalspecifier dalam deklarasi kelas untuk membatasi kelas lain dari mewarisi itu .

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
      virtual void __dummy() = 0;
};

Meskipun terdengar konyol dan tidak masuk akal, C ++ 11 memungkinkan deklarasi "fungsi virtual murni yang tidak dapat ditimpa", yang dapat Anda gunakan bersama mendeklarasikan kelas finaluntuk secara murni dan sepenuhnya menerapkan perilaku statis karena ini menghasilkan resultan kelas untuk tidak diwariskan dan fungsi dummy untuk tidak diganti dengan cara apa pun.

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
     // Other private declarations

     virtual void __dummy() = 0 final;
}; // Foo now exhibits all the properties of a static class
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.