Apakah C ++ 11 memiliki properti bergaya C #?


93

Di C #, ada gula sintaks yang bagus untuk bidang dengan pengambil dan penyetel. Selain itu, saya menyukai properti yang diimplementasikan secara otomatis yang memungkinkan saya untuk menulis

public Foo foo { get; private set; }

Di C ++ saya harus menulis

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

Apakah ada beberapa konsep seperti itu di C ++ 11 yang memungkinkan saya memiliki beberapa gula sintaks dalam hal ini?


62
Itu bisa dilakukan dengan beberapa makro. melarikan diri karena malu
Mankarse

7
@Eloff: Memublikasikan semuanya SELALU merupakan ide yang buruk.
Kaiserludi

8
Tidak ada konsep seperti itu! Dan Anda juga tidak membutuhkannya: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) pertanyaan ini cukup lama b) Saya meminta gula sintaks, yang memungkinkan saya untuk menghilangkan tanda kurung c) meskipun artikel menyajikan argumen yang valid terhadap properti adaptasi, apakah C ++ 'memerlukan atau tidak membutuhkan' properti sangat subjektif. C ++ adalah mesin Tur yang setara bahkan tanpa mereka, tetapi itu tidak berarti bahwa memiliki gula sintaks seperti itu akan membuat C ++ lebih produktif.
Radim Vansa

3
Tentu saja tidak.

Jawaban:


87

Di C ++ Anda dapat menulis fitur Anda sendiri. Berikut adalah contoh implementasi properti menggunakan kelas tanpa nama. Artikel Wikipedia

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

Anda dapat menulis getter & setter Anda sendiri di tempatnya dan jika Anda ingin akses anggota kelas pemegang, Anda dapat memperluas kode contoh ini.


1
Adakah ide tentang cara mengubah kode ini agar tetap memiliki variabel anggota pribadi yang dapat diakses oleh Foo secara internal, sementara API publik hanya mengekspos properti? Saya tentu saja dapat menjadikan Foo sebagai teman alpha / beta, tetapi kemudian saya masih harus menulis alpha.value, untuk mengakses nilainya, tetapi saya lebih suka jika mengakses langsung variabel anggota dari dalam Foo akan terasa lebih seperti mengakses anggota Foo itu sendiri dan bukan anggota kelas properti bertingkat khusus.
Kaiserludi

1
@Kaiserludi Ya: dalam hal ini, buat alpha dan bravo privat. Di Foo, Anda dapat membaca / menulis dengan "properti" di atas, tetapi di luar Foo, ini tidak mungkin lagi. Untuk menghindari itu, buatlah referensi const yang bersifat publik. Ini dapat diakses dari luar, tetapi hanya untuk membaca karena ini adalah referensi yang konstan. Satu-satunya peringatan adalah Anda memerlukan nama lain untuk referensi konstitusi publik. Saya pribadi akan menggunakan _alphavariabel privat dan alphauntuk referensi.
Kapichu

1
@Kapichu: bukan solusi, karena 2 alasan. 1) Dalam C #, pengambil / penyetel properti sering digunakan untuk menyematkan pemeriksaan keamanan yang dipaksa untuk pengguna publik kelas sambil mengizinkan fungsi anggota untuk mengakses nilai secara langsung. 2) referensi const tidak gratis: bergantung pada kompiler / platform, referensi akan diperbesar sizeof(Foo).
ceztko

@psx: karena keterbatasan pendekatan ini, saya akan menghindarinya dan menunggu penambahan yang tepat ke standar, jika itu akan muncul.
ceztko

@Kapichu: Tapi alpha dan bravo dalam kode contoh adalah propertinya. Saya ingin mengakses langsung variabel itu sendiri dari dalam implementasi Foo, tanpa perlu menggunakan properti, sementara saya hanya ingin mengekspos akses melalui properti di API.
Kaiserludi

53

C ++ tidak memiliki bawaan ini, Anda dapat menentukan template untuk meniru fungsionalitas properti:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

Untuk mendefinisikan properti :

Property<float> x;

Untuk mengimplementasikan pengambil / penyetel khusus cukup mewarisi:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

Untuk mendefinisikan properti read-only :

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

Dan untuk menggunakannya di kelasOwner :

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

Anda dapat menentukan beberapa hal di atas dalam makro agar lebih ringkas.


Saya ingin tahu apakah ini dikompilasi menjadi fitur biaya nol, saya tidak tahu apakah membungkus setiap anggota data dalam instance kelas akan menghasilkan jenis pengemasan struktur yang sama, misalnya.
Dai

1
Logika "pengambil / penyetel khusus" dapat dibuat lebih bersih secara sintaksis dengan menggunakan fungsi lambda, sayangnya Anda tidak dapat mendefinisikan lambda di luar konteks yang dapat dieksekusi di C ++ (belum!) Jadi tanpa menggunakan makro praprosesor Anda akan berakhir dengan kode yang hanya sama fiddly seperti getter / setter bodoh, yang sangat disayangkan.
Dai

2
"Kelas: ..." pada contoh terakhir menarik, dan hilang dari contoh lainnya. Ini membuat deklarasi teman yang diperlukan - tanpa memperkenalkan nama kelas baru.
Hans Olsson

Perbedaan besar antara ini dan jawaban 2010-Nov-19 adalah bahwa ini memungkinkan penimpaan pengambil atau penyetel berdasarkan kasus per kasus. Dengan cara itu seseorang dapat memeriksa masukan untuk berada dalam jangkauan, atau mengirim pemberitahuan perubahan untuk mengubah pendengar acara, atau tempat untuk menggantung breakpoint.
Eljay

Perhatikan bahwa virtualadalah mungkin perlu untuk kebanyakan kasus penggunaan, sejak Properti tidak mungkin digunakan polymorphically.
Eljay

28

Tidak ada dalam bahasa C ++ yang akan berfungsi di semua platform dan kompiler.

Tetapi jika Anda ingin merusak kompatibilitas lintas platform dan berkomitmen untuk kompiler tertentu, Anda mungkin dapat menggunakan sintaks tersebut, misalnya di Microsoft Visual C ++ Anda dapat melakukannya

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}


18

Anda dapat meniru pengambil dan penyetel sampai batas tertentu dengan memiliki anggota tipe khusus dan menimpa operator(type)dan operator=untuk itu. Apakah itu ide yang bagus adalah pertanyaan lain dan saya akan +1menjawab jawaban Kerrek SB untuk mengungkapkan pendapat saya atasnya :)


Anda dapat meniru pemanggilan metode saat menetapkan-ke atau membaca-dari menurut jenis tersebut, tetapi Anda tidak dapat membedakan siapa yang memanggil operasi penetapan (untuk melarangnya jika itu bukan pemilik bidang) - apa yang saya coba lakukan dengan menentukan akses yang berbeda level untuk pengambil dan penyetel.
Radim Vansa

@ Flavius: Cukup tambahkan a friendke pemilik bidang.
kennytm

17

Mungkin lihatlah kelas properti yang telah saya kumpulkan selama beberapa jam terakhir: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Ini memungkinkan Anda memiliki properti yang berperilaku seperti ini:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

Bagus, namun meskipun ini memungkinkan pengakses yang ditentukan pengguna, tidak ada fitur pengambil / penyetel pribadi publik yang saya cari.
Radim Vansa

17

Dengan C ++ 11 Anda dapat menentukan template kelas Properti dan menggunakannya seperti ini:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

Dan inilah template kelas Property.

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};

2
apa C::*maksudnya Saya belum pernah melihat yang seperti ini sebelumnya?
Rika

1
Ini adalah penunjuk ke fungsi anggota non-statis di kelas C. Ini mirip dengan penunjuk fungsi biasa tetapi untuk memanggil fungsi anggota Anda perlu menyediakan objek tempat fungsi tersebut dipanggil. Ini dicapai dengan garis itsObject->*itsSetter(theValue)pada contoh di atas. Lihat di sini untuk penjelasan lebih rinci tentang fitur ini.
Christoph Böhme

@Niceman, apakah ada contoh penggunaan? Tampaknya sangat mahal untuk menjadi anggota. Ini juga tidak terlalu berguna sebagai anggota statis.
Grim Fandango

16

Seperti yang telah dikatakan banyak orang lain, tidak ada dukungan bawaan dalam bahasa tersebut. Namun, jika Anda menargetkan kompiler Microsoft C ++, Anda dapat memanfaatkan ekstensi khusus Microsoft untuk properti yang didokumentasikan di sini.

Ini adalah contoh dari halaman tertaut:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

12

Tidak, C ++ tidak memiliki konsep properti. Meskipun sulit untuk mendefinisikan dan memanggil getThis () atau setThat (value), Anda membuat pernyataan kepada konsumen dari metode tersebut bahwa beberapa fungsionalitas dapat terjadi. Mengakses bidang di C ++, di sisi lain, memberi tahu konsumen bahwa tidak ada fungsionalitas tambahan atau tak terduga yang akan terjadi. Properti akan membuat hal ini kurang jelas karena akses properti pada pandangan pertama tampak bereaksi seperti bidang, tetapi pada kenyataannya bereaksi seperti metode.

Selain itu, saya bekerja di aplikasi .NET (CMS yang sangat terkenal) mencoba membuat sistem keanggotaan pelanggan. Karena cara mereka menggunakan properti untuk objek pengguna mereka, tindakan dijalankan yang tidak saya antisipasi, menyebabkan implementasi saya dieksekusi dengan cara yang aneh termasuk rekursi tak terbatas. Ini karena objek penggunanya melakukan panggilan ke lapisan akses data atau beberapa sistem cache global saat mencoba mengakses hal-hal sederhana seperti StreetAddress. Seluruh sistem mereka didasarkan pada apa yang saya sebut sebagai penyalahgunaan properti. Seandainya mereka menggunakan metode alih-alih properti, saya pikir saya akan lebih cepat mengetahui apa yang salah. Seandainya mereka telah menggunakan bidang (atau setidaknya membuat properti mereka berperilaku lebih seperti bidang), saya pikir sistem akan lebih mudah untuk diperluas dan dipelihara.

[Sunting] Mengubah pikiran saya. Aku mengalami hari yang buruk dan sedikit mengomel. Pembersihan ini harus lebih profesional.


11

Berdasarkan https://stackoverflow.com/a/23109533/404734, berikut adalah versi dengan pengambil publik dan penyetel pribadi:

struct Foo
{
    class
    {
            int value;
            int& operator= (const int& i) { return value = i; }
            friend struct Foo;
        public:
            operator int() const { return value; }
    } alpha;
};

4

Ini sebenarnya bukan properti, tetapi melakukan apa yang Anda inginkan dengan cara sederhana:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

Di sini X besar berperilaku seperti public int X { get; private set; }dalam sintaks C #. Jika Anda menginginkan properti full-blown, saya membuat bidikan pertama untuk menerapkannya di sini .


2
Ini bukan ide yang bagus. Setiap kali Anda membuat salinan objek dari kelas ini, referensi Xobjek baru akan tetap mengarah ke anggota objek lama, karena hanya disalin seperti anggota penunjuk. Ini sendiri buruk, tetapi ketika objek lama dihapus, Anda mendapatkan kerusakan memori di atasnya. Untuk membuat ini bekerja, Anda juga harus menerapkan konstruktor salinan Anda sendiri, operator penugasan, dan memindahkan konstruktor.
toster

4

Anda mungkin tahu itu tetapi saya hanya akan melakukan hal berikut:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

Pendekatan ini sederhana, tidak menggunakan trik cerdas dan menyelesaikan pekerjaan!

Masalahnya adalah bahwa beberapa orang tidak suka memberi awalan bidang pribadi mereka dengan garis bawah sehingga mereka tidak dapat benar-benar menggunakan pendekatan ini, tetapi untungnya bagi mereka yang melakukannya, ini sangat mudah. :)

Dapatkan dan setel prefiks tidak menambah kejelasan pada API Anda tetapi membuatnya lebih bertele-tele dan alasan saya rasa mereka tidak menambahkan informasi yang berguna adalah karena ketika seseorang perlu menggunakan API jika API masuk akal, dia mungkin akan menyadari apa itu tidak tanpa awalan.

Satu hal lagi, mudah untuk memahami bahwa ini adalah properti karena namebukan kata kerja.

Skenario terburuk, jika API konsisten dan orang tersebut tidak menyadari bahwa itu name()adalah aksesor dan name(value)mutator maka dia hanya perlu mencarinya sekali dalam dokumentasi untuk memahami polanya.

Sebanyak saya suka C # Saya rasa C ++ tidak membutuhkan properti sama sekali!


Mutator Anda masuk akal jika seseorang menggunakan foo(bar)(bukan yang lebih lambat foo = bar), tetapi pengakses Anda tidak ada hubungannya dengan properti sama sekali ...
Matthias

@Matthias Menyatakan bahwa itu tidak ada hubungannya dengan properti sama sekali, tidak memberi tahu saya apa-apa, dapatkah Anda menjelaskannya? selain itu saya tidak mencoba membandingkannya tetapi jika Anda membutuhkan mutator dan accessor Anda dapat menggunakan konvensi ini.
Eyal Solnik

Pertanyaannya adalah tentang konsep perangkat lunak Properties. Properti dapat digunakan seolah-olah mereka adalah anggota data publik (penggunaan), tetapi sebenarnya adalah metode khusus yang disebut pengakses (deklarasi). Solusi Anda bergantung pada metode (pengambil / penyetel biasa) untuk deklarasi dan penggunaan. Jadi ini, pertama-tama, jelas bukan penggunaan yang diminta OP, melainkan beberapa konvensi penamaan yang aneh dan tidak konvensional (dan dengan demikian, kedua, juga tidak ada gula sintaksis).
Matthias

Sebagai efek samping kecil, mutator Anda secara mengejutkan bekerja sebagai Properti, karena di C ++ paling baik diinisialisasi dengan foo(bar)alih - alih foo=baryang dapat dicapai dengan void foo(Bar bar)metode mutator pada _foovariabel anggota.
Matthias

@Matthias Saya tahu apa itu properti, saya telah menulis C ++ sedikit dan C # selama lebih dari satu dekade, saya tidak berdebat tentang manfaat properti dan apa itu tetapi saya tidak pernah BENAR-BENAR membutuhkannya di C ++, sebenarnya Anda Mengatakan bahwa mereka dapat digunakan sebagai data publik dan itu sebagian besar benar tetapi ada kasus di C # di mana Anda bahkan tidak dapat menggunakan properti secara langsung, seperti melewatkan properti oleh ref sedangkan dengan bidang publik, Anda bisa.
Eyal Solnik

4

Tidak .. Tetapi Anda harus mempertimbangkan jika hanya get: set function dan tidak ada tugas tambahan yang dibentuk sebelumnya di dalam get: set, buat saja menjadi publik.


2

Saya mengumpulkan ide-ide dari beberapa sumber C ++ dan memasukkannya ke dalam contoh yang bagus, masih cukup sederhana untuk getter / setter di C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Keluaran:

new canvas 256 256
resize to 128 256
resize to 128 64

Anda dapat mengujinya secara online di sini: http://codepad.org/zosxqjTX


Ada overhead memori untuk menjaga refensi diri, + sintaks akward ctor.
Red. Gelombang

Untuk mengusulkan properti? Saya kira ada banyak proposal yang ditolak.
Red. Gelombang

@ Red. Gelombang Sujudlah pada tuan dan tuan penolakan. Selamat datang di C ++. Clang dan MSVC memiliki ekstensi khusus untuk properti, jika Anda tidak menginginkan referensi mandiri.
lama12345

Saya tidak pernah bisa membungkuk. Tidak semua fitur cocok menurut saya. Bagi saya, Objek jauh lebih dari sekedar sepasang fungsi penyetel + pengambil. Saya telah mencoba implementasi sendiri untuk menghindari overhead memori permanen yang tidak perlu, tetapi sintaksis dan tugas mendeklarasikan instance tidak memuaskan; Saya tertarik untuk menggunakan makro deklaratif, tetapi saya bukan penggemar makro. Dan pendekatan saya akhirnya, menyebabkan properti diakses dengan sintaks fungsi, yang banyak termasuk saya tidak setuju.
Red. Gelombang

0

Apakah kelas Anda benar-benar perlu memberlakukan beberapa invarian atau apakah itu hanya pengelompokan logis dari elemen anggota? Jika yang terakhir, Anda harus mempertimbangkan untuk membuat hal itu menjadi struct dan mengakses anggota secara langsung.


0

Ada satu set makro yang tertulis di sini . Ini memiliki deklarasi properti yang nyaman untuk tipe nilai, tipe referensi, tipe hanya baca, tipe kuat dan tipe lemah.

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

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