C ++ 11'in C # tarzı özellikleri var mı?


93

C # 'da alıcı ve ayarlayıcılı alanlar için güzel bir sözdizimi şekeri vardır. Dahası, otomatik olarak uygulanan özellikleri de yazmama izin veren

public Foo foo { get; private set; }

C ++ ile yazmalıyım

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

C ++ 11'de bu konuda biraz sözdizimi şekeri almama izin veren böyle bir kavram var mı?


64
Birkaç makro ile yapılabilir. utanç içinde
kaçıyor

7
@Eloff: Her şeyi halka açık hale getirmek HER ZAMAN kötü bir fikirdir.
Kaiserludi

8
Böyle bir kavram yok! Ve buna da ihtiyacınız yok: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) bu soru oldukça eski bir soru b) parantezlerden kurtulmamı sağlayacak sözdizimi şekeri istiyordum c) makale uyarlama özelliklerine karşı geçerli argümanlar sunsa da, C ++ 'özelliklere ihtiyaç duysun veya gerek duymasın çok özneldir. C ++, onlar olmadan bile Touring makinesi eşdeğeridir, ancak bu, böyle bir sözdizimi şekerine sahip olmanın C ++ 'yı daha üretken hale getireceği anlamına gelmez.
Radim Vansa

3
Kesinlikle hayır.

Yanıtlar:


88

C ++ 'da kendi özelliklerinizi yazabilirsiniz. İsimsiz sınıfların kullanıldığı örnek bir özellik uygulaması. Wikipedia makalesi

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

Kendi alıcılarınızı ve ayarlayıcılarınızı yerinde yazabilir ve sahip sınıf üyesi erişimini istiyorsanız bu örnek kodu uzatabilirsiniz.


1
Foo'nun dahili olarak erişebileceği özel bir üye değişkenine sahip olmak için bu kodun nasıl değiştirileceği hakkında herhangi bir fikriniz var mı? Elbette Foo'yu alfa / beta'nın bir arkadaşı yapabilirdim, ancak o zaman değere erişmek için yine de alpha.value yazmam gerekecek, ancak üye değişkenine Foo'nun içinden doğrudan erişmenin daha çok hissettirmesini tercih ederim. Foo'nun bir üyesine erişme ve özel bir yuvalanmış özellik sınıfının bir üyesi değil.
Kaiserludi

1
@Kaiserludi Evet: Bu durumda alpha ve bravo'yu özel yapın. Foo'da, yukarıdaki "özellikler" ile okuyabilir / yazabilirsiniz, ancak Foo dışında bu artık mümkün olmayacaktır. Bunu aşmak için, herkese açık bir const referansı yapın. Dışarıdan erişilebilir, ancak sürekli bir referans olduğu için sadece okumak için. Tek uyarı, public const referansı için başka bir isme ihtiyacınız olacağıdır. Şahsen _alphaözel değişken ve alphareferans için kullanırdım.
Kapichu

1
@Kapichu: 2 nedenden dolayı bir çözüm değil. 1) C # özellikte alıcılar / ayarlayıcılar, üye işlevlerin değere doğrudan erişmesine izin verirken, sınıfın genel kullanıcılarına zorlanan güvenlik kontrollerini gömmek için sıklıkla kullanılır. 2) const referansları ücretsiz değildir: derleyiciye / platforma bağlı olarak genişler sizeof(Foo).
ceztko

@psx: Bu yaklaşımın sınırlamaları nedeniyle, bundan kaçınırım ve eğer ortaya çıkacaksa, standarda uygun şekilde eklenmesini beklerdim.
ceztko

@Kapichu: Ancak örnek koddaki alpha ve bravo özelliklerdir. Değişkenin kendisine, bir özelliği kullanmaya gerek kalmadan, Foo uygulamasının içinden doğrudan erişmek, ancak yalnızca API'deki bir özellik aracılığıyla erişimi açığa çıkarmak istiyorum.
Kaiserludi

56

C ++ 'da bu yerleşik değildir, özellikler işlevselliğini taklit etmek için bir şablon tanımlayabilirsiniz :

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

İçin bir özellik tanımlamak :

Property<float> x;

Özel bir alıcı / ayarlayıcı uygulamak için sadece devralın:

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;

Salt okunur bir özellik tanımlamak için :

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

Ve sınıfta kullanmak içinOwner :

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

Yukarıdakilerden bazılarını daha kısa hale getirmek için makrolarda tanımlayabilirsiniz .


Bunun sıfır maliyetli bir özelliğe derlenip derlenmediğini merak ediyorum, örneğin bir sınıf örneğinde her bir veri üyesini sarmalamanın aynı türden bir yapı paketlemesine neden olup olmayacağını bilmiyorum.
Dai

1
"Özel alıcı / ayarlayıcı" mantığı, lambda işlevlerinin kullanılmasıyla sözdizimsel olarak daha temiz hale getirilebilir, ne yazık ki, C ++ 'da çalıştırılabilir bir bağlamın dışında bir lambda tanımlayamazsınız (henüz!), Bu nedenle bir önişlemci makrosu kullanmadan sadece bir kod elde edersiniz. aptal alıcılar / ayarlayıcılar kadar kurnazca, ki bu talihsiz bir durumdur.
Dai

2
Son örnekteki "sınıf: ..." ilginç ve diğer örneklerde eksik. Yeni bir sınıf adı eklemeden gerekli arkadaş beyanını yaratır.
Hans Olsson

Bu ve 2010-Kasım-19 cevabı arasındaki büyük bir fark, bunun, duruma göre alıcı veya ayarlayıcıyı geçersiz kılmaya olanak sağlamasıdır. Bu şekilde, girişin aralık içinde olup olmadığı incelenebilir veya olay dinleyicilerini değiştirmek için bir değişiklik bildirimi gönderilebilir veya bir kesme noktası asılacak bir yer gönderilebilir.
Eljay

O Not virtualolduğu muhtemelen bir Mülkiyet polymorphically kullanımını olması pek mümkün değildir, çünkü çoğu kullanım durumları için gereksiz.
Eljay

28

C ++ dilinde tüm platformlarda ve derleyicilerde çalışacak hiçbir şey yoktur.

Ancak, platformlar arası uyumluluğu bozmaya ve belirli bir derleyiciyi taahhüt etmeye istekliyseniz, bu tür bir sözdizimini kullanabilirsiniz, örneğin Microsoft Visual C ++ ' da

// 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;  
}

2
Bu aynı zamanda clang
Passer By

18

Adanmış türde bir üyeye sahip olarak operator(type)ve operator=bunun için geçersiz kılarak alıcı ve ayarlayıcıyı bir dereceye kadar taklit edebilirsiniz . İyi bir fikir olup olmadığı başka bir soru ve ben bu +1konudaki fikrimi ifade etmek için Kerrek SB'nin cevabına gidiyorum :)


Böyle bir türe göre atama veya okuma üzerine bir yöntemi çağırmayı taklit edebilirsiniz, ancak atama işlemini kimin çağırdığını ayırt edemezsiniz (alan sahibi değilse bunu engellemek için) - farklı erişim belirleyerek yapmaya çalıştığım şey alıcı ve ayarlayıcı için seviye.
Radim Vansa

@Flavius: friendAlan sahibine bir eklemeniz yeterli .
kennytm

17

Belki son saatlerde topladığım mülk sınıfına bir göz atabilirsiniz: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Aşağıdaki gibi davranan özelliklere sahip olmanızı sağlar:

CTestClass myClass = CTestClass();

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

Güzel, ancak bu kullanıcı tanımlı erişimcilere izin vermesine rağmen, aradığım genel alıcı / özel ayarlayıcı özelliği yok.
Radim Vansa

17

C ++ 11 ile bir Özellik sınıfı şablonu tanımlayabilir ve şu şekilde kullanabilirsiniz:

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

Ve işte burada Özellik sınıfı şablonu.

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
ne anlama C::*geliyor? Daha önce hiç böyle bir şey görmedim mi?
Rika

1
Bu, sınıftaki statik olmayan üye işlevine bir göstericidir C. Bu, düz bir işlev işaretçisine benzer, ancak üye işlevini çağırmak için işlevin çağrıldığı bir nesne sağlamanız gerekir. Bu, itsObject->*itsSetter(theValue)yukarıdaki örnekteki satırla elde edilir . Bu özelliğin daha ayrıntılı açıklaması için buraya bakın .
Christoph Böhme

@Niceman, kullanım örneği var mı? Üye olmak çok maliyetli görünüyor. Statik üye olarak da özellikle kullanışlı değildir.
Grim Fandango

16

Diğerlerinin de söylediği gibi, dilde yerleşik bir destek yok. Ancak, Microsoft C ++ derleyicisini hedefliyorsanız, burada belgelenen özellikler için Microsoft'a özgü uzantıdan yararlanabilirsiniz .

Bu, bağlantılı sayfadaki örnektir:

// 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

Hayır, C ++ 'nın özellik kavramı yoktur. GetThis () veya setThat (değer) 'i tanımlamak ve çağırmak zor olsa da, tüketiciye bazı işlevlerin ortaya çıkabileceği konusunda bu yöntemlerin ifadesini veriyorsunuz. C ++ 'daki alanlara erişim ise tüketiciye hiçbir ek veya beklenmedik işlevin oluşmayacağını bildirir. Mülk erişimi, ilk bakışta bir alan gibi tepki verdiğinden, ancak aslında bir yöntem gibi tepki verdiğinden, özellikler bunu daha az belirgin hale getirir.

Bir kenara, bir müşteri üyelik sistemi oluşturmaya çalışan bir .NET uygulamasında (çok iyi bilinen bir CMS) çalışıyordum. Kullanıcı nesneleri için özellikleri kullanma biçimleri nedeniyle, beklemediğim eylemler ateşleniyor ve uygulamalarımın sonsuz özyineleme dahil tuhaf şekillerde yürütülmesine neden oluyordu. Bunun nedeni, kullanıcı nesnelerinin StreetAddress gibi basit şeylere erişmeye çalışırken veri erişim katmanına veya bazı küresel önbelleğe alma sistemlerine çağrı yapmasıydı. Tüm sistemleri, mülklerin kötüye kullanılması dediğim şey üzerine kuruldu. Mülkler yerine yöntemler kullanmış olsalardı, neyin yanlış gittiğini çok daha çabuk anlardım. Alanları kullansalar (veya en azından özelliklerinin daha çok alan gibi davranmasını sağlasalar), sistemi genişletmek ve sürdürmek daha kolay olurdu.

[Düzenle] Düşüncelerimi değiştirdim. Kötü bir gün geçirdim ve biraz rant yaptım. Bu temizlik daha profesyonel olmalı.


12

Https://stackoverflow.com/a/23109533/404734 temel alınarak burada genel alıcı ve özel ayarlayıcıya sahip bir sürüm bulunmaktadır:

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

4

Bu tam olarak bir mülk değildir, ancak istediğiniz şeyi basit bir şekilde yapar:

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

Burada büyük X public int X { get; private set; }, C # sözdizimindeki gibi davranır . Tam gelişmiş özellikler istiyorsanız, bunları burada uygulamak için ilk atışı yaptım .


2
Bu iyi bir fikir değil. Bu sınıftaki bir nesnenin bir kopyasını yaptığınızda X, yeni nesnenin referansı yine de eski nesnenin üyesine işaret edecektir, çünkü yalnızca bir işaretçi üyesi gibi kopyalanır. Bu kendi başına kötüdür, ancak eski nesne silindiğinde, bunun üzerine bellek bozulması da gelir. Bunun çalışması için, kendi kopya oluşturucunuzu, atama operatörünüzü ve taşıma yapıcınızı da uygulamanız gerekir.
2017

4

Muhtemelen bunu biliyorsunuzdur ama ben sadece şunu yapardım:

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

Bu yaklaşım basittir, akıllıca numaralar kullanmaz ve işi halleder!

Sorun şu ki, bazı insanlar özel alanlarının önüne bir alt çizgi koymaktan hoşlanmıyorlar ve bu yüzden bu yaklaşımı gerçekten kullanamıyorlar, ancak neyse ki bunu yapanlar için bu gerçekten basit. :)

Get ve set önekleri API'nize netlik katmaz, ancak onları daha ayrıntılı hale getirir ve yararlı bilgiler eklediklerini düşünmememin nedeni, API'nin mantıklı olması durumunda birisinin bir API kullanması gerektiğinde muhtemelen bunun farkına varmasıdır. önekler olmadan yapar.

Bir şey daha, bunların özellikler olduğunu kavramak kolay çünkü name bir fiil değildir.

En kötü durum senaryosu, API'ler tutarlıysa ve kişi bunun name()bir erişimci olduğunu anlamadıysa vename(value) bir mutatör olduğunu anlamadıysa, modeli anlamak için dokümantasyonda yalnızca bir kez bakması gerekecektir.

Ne kadar C # sevsem de C ++ 'nın özelliklere ihtiyacı olduğunu düşünmüyorum!


Mutatörleriniz foo(bar)(daha yavaş yerine foo = bar) kullanırsa mantıklıdır , ancak erişimcilerin özelliklerle hiçbir ilgisi yoktur ...
Matthias

@Matthias Mülklerle hiçbir ilgisi olmadığını belirten, bana hiçbir şey söylemedi, detaylandırır mısınız? ayrıca onları karşılaştırmaya çalışmadım ama bir mutatöre ve erişimciye ihtiyacınız varsa bu kuralı kullanabilirsiniz.
Eyal Solnik

Soru, Özellikler'in yazılım konseptiyle ilgilidir. Özellikler, genel veri üyeleri gibi kullanılabilir (kullanım), ancak aslında erişimciler (bildirim) adı verilen özel yöntemlerdir. Çözümünüz, beyan ve kullanım için yöntemlere (sıradan alıcılar / ayarlayıcılar) yapışır. Yani bu, her şeyden önce, kesinlikle OP'nin istediği kullanım değil, daha çok garip ve alışılmadık bir adlandırma kuralı (ve dolayısıyla, ikinci olarak, sözdizimsel şeker de yok).
Matthias

Küçük bir yan etki olarak, mutatörünüz şaşırtıcı bir şekilde bir Özellik olarak çalışır, çünkü C ++ 'da biri en iyi şekilde başlatılır, foo(bar)bunun yerine bir üye değişkeni üzerinde bir mutatör yöntemi foo=barile elde edilebilir . void foo(Bar bar)_foo
Matthias

@Matthias Özelliklerin ne olduğunu biliyorum, on yıldan fazla bir süredir C ++ ve C # yazıyorum, özelliklerin faydaları ve ne oldukları hakkında tartışmıyorum ama C ++ 'da GERÇEKTEN onlara hiç ihtiyacım olmadı, aslında sen Bunların genel veri olarak kullanılabileceğini söylüyorum ve bu çoğunlukla doğrudur, ancak C # 'da özellikleri doğrudan kullanamayacağınız durumlar vardır, örneğin bir mülkü ref ile geçirmek gibi, ancak genel bir alan ile bunu yapabilirsiniz.
Eyal Solnik

4

Hayır .. Ama sadece get: set işlevi olup olmadığını ve get: set metodlarının içinde önceden oluşturulmuş ek görev olmadığını düşünmelisiniz.


2

Fikirleri birden fazla C ++ kaynağından topladım ve C ++ 'daki alıcılar / ayarlayıcılar için güzel, yine de oldukça basit bir örneğe koydum:

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

Çıktı:

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

Çevrimiçi olarak burada test edebilirsiniz: http://codepad.org/zosxqjTX


Kendi kendine referansları korumak için bir bellek ek yükü, + bir akward ctor sözdizimi vardır.
Red.Wave

Mülkler önermek için mi? Sanırım bu tür teklifler reddedildi.
Red.Wave

@ Red.Wave O halde retin efendisine ve efendisine boyun eğ. C ++ 'ya hoş geldiniz. Öz referansları istemiyorsanız, Clang ve MSVC, özellikler için özel uzantılara sahiptir.
lama12345

Asla eğemem. Her özelliğin bile uygun olmadığını düşünüyorum. Bana göre Nesneler, ayarlayıcı + alıcı işlevlerinden çok daha fazlasıdır. Gereksiz kalıcı bellek yükünden kaçınarak kendi uygulamalarını denedim, ancak örnekleri bildirme sözdizimi ve işi tatmin edici değildi; Bildirimsel makrolar kullanmaya meraklıydım, ancak yine de makroların büyük bir hayranı değilim. Ve benim yaklaşımım sonunda, kendim dahil birçok kişinin onaylamadığı işlev sözdizimi ile erişilen yeteneklere yol açtı.
Red.Wave

0

Sınıfınızın gerçekten bazı değişmezleri zorlaması mı gerekiyor yoksa bu sadece üye öğelerin mantıksal bir gruplaması mı? İkincisi ise, şeyi bir yapı haline getirmeyi ve üyelere doğrudan erişmeyi düşünmelisiniz.


0

Yazılı makrolar kümesi vardır İşte . Bu, değer türleri, referans türleri, salt okunur türler, güçlü ve zayıf türler için uygun özellik bildirimlerine sahiptir.

class MyClass {

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

 public:
 ...

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.