Czy C ++ 11 ma właściwości w stylu C #?


93

W C # jest ładny cukier składniowy dla pól z getter i setter. Ponadto podoba mi się auto-implementowane właściwości, które pozwalają mi pisać

public Foo foo { get; private set; }

W C ++ muszę pisać

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

Czy jest jakaś taka koncepcja w C ++ 11, która pozwala mi mieć trochę cukru składniowego w tym?


64
Można to zrobić za pomocą kilku makr. ucieka ze wstydu
Mankarse

7
@Eloff: Po prostu upublicznianie wszystkiego jest ZAWSZE złym pomysłem.
Kaiserludi

8
Nie ma takiej koncepcji! Ty też tego nie potrzebujesz: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) to pytanie jest dość stare b) prosiłem o cukier składniowy, który pozwoliłby mi pozbyć się nawiasów c) chociaż artykuł przedstawia uzasadnione argumenty przeciwko dostosowywaniu właściwości, czy C ++ „potrzebuje” właściwości, czy nie jest bardzo subiektywna. C ++ jest odpowiednikiem maszyny Touring nawet bez nich, ale to nie znaczy, że posiadanie takiego cukru składniowego zwiększyłoby produktywność C ++.
Radim Vansa

3
Absolutnie nie.

Odpowiedzi:


88

W C ++ możesz pisać własne funkcje. Oto przykładowa implementacja właściwości przy użyciu nienazwanych klas. Artykuł w Wikipedii

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

Możesz napisać własne metody pobierające i ustawiające, a jeśli chcesz mieć dostęp do członka klasy posiadacza, możesz rozszerzyć ten przykładowy kod.


1
Jakieś pomysły na to, jak zmodyfikować ten kod, aby nadal mieć prywatną zmienną składową, do której Foo ma dostęp wewnętrznie, podczas gdy publiczny interfejs API ujawnia tylko właściwość? Mógłbym oczywiście po prostu uczynić Foo przyjacielem alfa / beta, ale nadal musiałbym napisać alpha.value, aby uzyskać dostęp do wartości, ale wolałbym, aby bezpośredni dostęp do zmiennej składowej z wnętrza Foo był bardziej podobny do dostęp do elementu członkowskiego samego Foo, a nie członka specjalnej zagnieżdżonej klasy właściwości.
Kaiserludi

1
@Kaiserludi Tak: w tym przypadku ustaw alfa i bravo jako prywatne. W Foo możesz czytać / pisać z tymi "właściwościami" powyżej, ale poza Foo nie byłoby to już możliwe. Aby to obejść, utwórz odwołanie do const, które jest publiczne. Jest dostępny z zewnątrz, ale tylko do czytania, ponieważ jest stałym punktem odniesienia. Jedynym zastrzeżeniem jest to, że będziesz potrzebować innej nazwy dla publicznego odniesienia do stałej. Osobiście użyłbym _alphadla zmiennej prywatnej i alphadla odniesienia.
Kapichu,

1
@Kapichu: nie jest rozwiązaniem z 2 powodów. 1) W języku C # metody pobierające / ustawiające właściwości są często używane do osadzania kontroli bezpieczeństwa, które są wymuszane na publicznych użytkownikach klasy, jednocześnie zezwalając funkcjom członkowskim na bezpośredni dostęp do wartości. 2) odwołania do const nie są darmowe: w zależności od kompilatora / platformy będą się powiększać sizeof(Foo).
ceztko

@psx: ze względu na ograniczenia tego podejścia bym go unikał i czekał na odpowiednie dodanie do standardu, jeśli kiedykolwiek się pojawi.
ceztko

@Kapichu: Ale alpha i bravo w przykładowym kodzie to właściwości. Chciałbym mieć bezpośredni dostęp do samej zmiennej z wnętrza implementacji Foo, bez potrzeby używania właściwości, podczas gdy chciałbym ujawnić dostęp tylko przez właściwość w API.
Kaiserludi

55

C ++ nie ma tego wbudowanego, możesz zdefiniować szablon, aby naśladować funkcjonalność właściwości:

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

Aby zdefiniować właściwość :

Property<float> x;

Aby zaimplementować niestandardowy getter / setter, po prostu dziedzicz:

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;

Aby zdefiniować właściwość tylko do odczytu :

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

I do wykorzystania w klasieOwner :

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

Możesz zdefiniować niektóre z powyższych w makrach, aby były bardziej zwięzłe.


Jestem ciekawy, czy to skompiluje się do funkcji o zerowym koszcie, nie wiem, czy zawijanie każdego elementu członkowskiego danych w instancji klasy spowoduje na przykład ten sam rodzaj pakowania struktury.
Dai,

1
Logika "niestandardowego programu pobierającego / ustawiającego" może być poprawiona składniowo przez użycie funkcji lambda, niestety nie możesz zdefiniować lambdy poza kontekstem wykonywalnym w C ++ (jeszcze!), Więc bez użycia makra preprocesora otrzymujesz kod, który jest po prostu tak skrzypiące jak głupie metody pobierające / ustawiające, co jest niefortunne.
Dai

2
„Klasa: ...” w ostatnim przykładzie jest interesująca i brakuje jej w innych przykładach. Tworzy niezbędną deklarację znajomego - bez wprowadzania nowej nazwy klasy.
Hans Olsson

Duża różnica między tą odpowiedzią a odpowiedzią z 19 listopada 2010 r. Polega na tym, że umożliwia to zastąpienie metody pobierającej lub ustawiającej w zależności od przypadku. W ten sposób można sprawdzić, czy wejście znajduje się w zasięgu, lub wysłać powiadomienie o zmianie w celu zmiany detektorów zdarzeń lub miejsca, w którym można zawiesić punkt przerwania.
Eljay

Zauważ, że virtualjest prawdopodobnie niepotrzebne w większości przypadków użycia, ponieważ właściwość prawdopodobnie nie będzie używana polimorficznie.
Eljay

28

W języku C ++ nie ma nic, co działałoby na wszystkich platformach i kompilatorach.

Ale jeśli chcesz złamać kompatybilność między platformami i zdecydować się na konkretny kompilator, możesz użyć takiej składni, na przykład w Microsoft Visual C ++, możesz to zrobić

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

Możesz do pewnego stopnia emulować getter i setter, mając członka dedykowanego typu i nadpisując operator(type)i operator=dla niego. Czy to dobry pomysł, to kolejne pytanie i idę do odpowiedzi +1Kerreka SB, aby wyrazić swoją opinię na ten temat :)


Możesz emulować wywołanie metody po przypisaniu lub odczytaniu z tego typu, ale nie możesz odróżnić, kto wywołuje operację przypisania (aby zabronić tego, jeśli nie jest to właściciel pola) - co próbuję zrobić, określając inny dostęp poziom dla gettera i settera.
Radim Vansa,

@Flavius: Po prostu dodaj a frienddo właściciela pola.
kennytm

17

Może spójrz na klasę właściwości, którą utworzyłem w ciągu ostatnich godzin: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Pozwala mieć właściwości zachowujące się w ten sposób:

CTestClass myClass = CTestClass();

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

Fajnie, chociaż pozwala to na dostęp zdefiniowane przez użytkownika, nie ma funkcji publicznego pobierania / ustawiania prywatnego, której szukałem.
Radim Vansa

17

W C ++ 11 możesz zdefiniować szablon klasy właściwości i używać go w następujący sposób:

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

A oto szablon klasy 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
co to C::*znaczy Nigdy wcześniej nie widziałem czegoś takiego?
Rika

1
Jest to wskaźnik do niestatycznej funkcji składowej w klasie C. Jest to podobne do zwykłego wskaźnika funkcji, ale aby wywołać funkcję składową, musisz podać obiekt, na którym ta funkcja jest wywoływana. Osiąga się to za pomocą linii itsObject->*itsSetter(theValue)w powyższym przykładzie. Zobacz tutaj, aby uzyskać bardziej szczegółowy opis tej funkcji.
Christoph Böhme,

@Niceman, czy istnieje przykład użycia? Bycie członkiem wydaje się bardzo kosztowne. Nie jest też szczególnie przydatna jako statyczny element członkowski.
Grim Fandango

16

Jak już powiedziało wielu innych, nie ma wbudowanej obsługi języka. Jeśli jednak celujesz w kompilator Microsoft C ++, możesz skorzystać z rozszerzenia specyficznego dla firmy Microsoft dla właściwości, które jest udokumentowane tutaj.

Oto przykład z połączonej strony:

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

Nie, C ++ nie ma pojęcia właściwości. Chociaż zdefiniowanie i wywołanie metody getThis () lub setThat (wartość) może być niewygodne, użytkownik informuje użytkownika o tych metodach, że mogą wystąpić pewne funkcje. Z drugiej strony dostęp do pól w C ++ informuje konsumenta, że ​​nie wystąpią żadne dodatkowe lub nieoczekiwane funkcje. Właściwości sprawiłyby, że byłoby to mniej oczywiste, ponieważ dostęp do właściwości na pierwszy rzut oka wydaje się reagować jak pole, ale w rzeczywistości reaguje jak metoda.

Nawiasem mówiąc, pracowałem w aplikacji .NET (bardzo dobrze znanym CMS) próbując stworzyć system członkostwa klientów. Ze względu na sposób, w jaki używali właściwości dla swoich obiektów użytkowników, uruchamiane były akcje, których nie przewidziałem, powodując, że moje implementacje wykonywały się w dziwaczny sposób, w tym w nieskończoną rekurencję. Było tak, ponieważ ich obiekty użytkowników wywoływały warstwę dostępu do danych lub jakiś globalny system buforowania, próbując uzyskać dostęp do prostych rzeczy, takich jak StreetAddress. Cały ich system opierał się na czymś, co nazwałbym nadużyciem własności. Gdyby zamiast właściwości zastosowali metody, myślę, że o wiele szybciej zorientowałbym się, co jest nie tak. Gdyby użyli pól (lub przynajmniej sprawili, że ich właściwości zachowywały się bardziej jak pola), myślę, że system byłby łatwiejszy w rozbudowie i utrzymaniu.

[Edytuj] Zmieniłem moje myśli. Miałem zły dzień i trochę poszedłem na tyradę. To czyszczenie powinno być bardziej profesjonalne.



4

To nie jest dokładnie właściwość, ale robi to, co chcesz w prosty sposób:

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

Tutaj duży X zachowuje się jak public int X { get; private set; }w składni C #. Jeśli chcesz pełnowartościowych właściwości, zrobiłem pierwsze zdjęcie, aby je tutaj wdrożyć .


2
To nie jest dobry pomysł. Za każdym razem, gdy tworzysz kopię obiektu tej klasy, odniesienie Xdo nowego obiektu będzie nadal wskazywało na element składowy starego obiektu, ponieważ jest on po prostu kopiowany jak element wskaźnika. Jest to złe samo w sobie, ale kiedy stary obiekt zostanie usunięty, pojawi się uszkodzenie pamięci. Aby to zadziałało, musiałbyś również zaimplementować własny konstruktor kopiujący, operator przypisania i konstruktor przenoszenia.
wystartował

4

Prawdopodobnie wiesz o tym, ale zrobiłbym po prostu, co następuje:

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

To podejście jest proste, nie wykorzystuje sprytnych sztuczek i wykonuje swoją pracę!

Problem polega jednak na tym, że niektórzy ludzie nie lubią poprzedzać swoich prywatnych pól podkreśleniem, więc tak naprawdę nie mogą używać tego podejścia, ale na szczęście dla tych, którzy to robią, jest to naprawdę proste. :)

Prefiksy get and set nie dodają jasności do twojego API, ale sprawiają, że są bardziej szczegółowe, a powodem, dla którego nie sądzę, aby dodawały użyteczne informacje, jest to, że gdy ktoś musi użyć API, jeśli API ma sens, prawdopodobnie zdaje sobie sprawę, co to jest działa bez przedrostków.

I jeszcze jedno, łatwo zrozumieć, że są to właściwości, ponieważ namenie jest to czasownik.

W najgorszym przypadku, jeśli interfejsy API są spójne, a osoba nie zdawała sobie sprawy, że name()jest akcesorium i name(value)jest mutatorem, będzie musiała tylko raz to sprawdzić w dokumentacji, aby zrozumieć wzorzec.

Chociaż kocham C #, nie sądzę, żeby C ++ w ogóle potrzebował właściwości!


Twoje mutatory mają sens, jeśli ktoś używa foo(bar)(zamiast wolniejszych foo = bar), ale twoje akcesory nie mają w ogóle nic wspólnego z właściwościami ...
Matthias

@Matthias Stwierdzając, że nie ma to nic wspólnego z właściwościami, nic mi nie mówi, czy możesz to rozwinąć? poza tym nie próbowałem ich porównywać, ale jeśli potrzebujesz mutatora i akcesorium, możesz użyć tej konwencji.
Eyal Solnik

Pytanie dotyczy koncepcji oprogramowania Właściwości. Właściwości mogą być używane tak, jakby były publicznymi członkami danych (użycie), ale w rzeczywistości są to specjalne metody zwane akcesorami (deklaracja). Twoje rozwiązanie trzyma się metod (zwykłych pobierających / ustawiających) dla deklaracji i użycia. Po pierwsze, zdecydowanie nie jest to użycie, o które prosi OP, ale raczej dziwna i niekonwencjonalna konwencja nazewnictwa (a zatem, po drugie, również brak cukru syntaktycznego).
Matthias

Jako niewielki efekt uboczny, Twój mutator zadziwiająco działa jako Właściwość, ponieważ w C ++ najlepiej inicjuje się z, foo(bar)a zamiast tego foo=barmożna to osiągnąć za pomocą void foo(Bar bar)metody mutatora na _foozmiennej składowej .
Matthias

@Matthias Wiem, jakie są właściwości, piszę C ++ całkiem sporo i C # od ponad dekady, nie kłócę się o korzyści z właściwości i czym one są, ale NAPRAWDĘ ich nie potrzebowałem w C ++, w rzeczywistości ty Mówię, że mogą być używane jako dane publiczne i to w większości prawda, ale są przypadki w C #, w których nie można nawet używać właściwości bezpośrednio, jak na przykład przekazywanie właściwości przez ref, podczas gdy w przypadku pola publicznego jest to możliwe.
Eyal Solnik

4

Nie. Ale powinieneś rozważyć, czy to jest po prostu funkcja get: set i żadne dodatkowe zadanie nie jest wykonywane wewnątrz metody get: set, po prostu upublicznij ją.


2

Zebrałem pomysły z wielu źródeł C ++ i umieściłem je w ładnym, wciąż dość prostym przykładzie pobierania / ustawiania w 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;
}

Wynik:

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

Możesz to przetestować online tutaj: http://codepad.org/zosxqjTX


Istnieje narzut pamięci związany z utrzymywaniem własnych odniesień + składnia akwarda ctora.
Red.Wave

Zaproponować nieruchomości? Wydaje mi się, że było sporo odrzuconych takich propozycji.
Red.Wave

@ Red.Wave Skłoń się do pana i pana odrzucenia. Witamy w C ++. Clang i MSVC mają niestandardowe rozszerzenia właściwości, jeśli nie chcesz, aby odwołania do siebie.
lama12345

Nigdy nie mogę się kłaniać. Myślę, że nie każda funkcja jest nawet odpowiednia. Dla mnie obiekty to znacznie więcej niż para funkcji ustawiających i pobierających. Próbowałem własnych implementacji, unikając niepotrzebnego stałego obciążenia pamięci, ale składnia i obowiązek deklarowania instancji nie były zadowalające; Przyciągnęło mnie używanie deklaratywnych makr, ale i tak nie jestem wielkim fanem makr. I w końcu moje podejście doprowadziło do właściwości, do których można uzyskać dostęp za pomocą składni funkcji, których wielu, w tym ja, nie akceptuje.
Red.Wave

0

Czy twoja klasa naprawdę musi wymusić jakąś niezmienną, czy jest to tylko logiczne zgrupowanie elementów składowych? Jeśli to drugie, powinieneś rozważyć utworzenie struktury i bezpośredni dostęp do członków.


0

Jest to zestaw makr napisanych tutaj . Ma wygodne deklaracje właściwości dla typów wartości, typów odwołań, typów tylko do odczytu, typów silnych i słabych.

class MyClass {

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

 public:
 ...

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.