C ++ 11 ha proprietà in stile C #?


93

In C #, c'è una bella sintassi sugar per i campi con getter e setter. Inoltre, mi piacciono le proprietà implementate automaticamente che mi consentono di scrivere

public Foo foo { get; private set; }

In C ++ devo scrivere

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

C'è qualche concetto di questo tipo in C ++ 11 che mi consente di avere un po 'di zucchero sintattico su questo?


62
Potrebbe essere fatto con un paio di macro. scappa per la vergogna
Mankarse

7
@Eloff: Rendere tutto pubblico è SEMPRE una cattiva idea.
Kaiserludi

8
Non esiste un tale concetto! E non ne hai bisogno anche tu
CinCout

2
a) questa domanda è piuttosto vecchia b) stavo chiedendo lo zucchero di sintassi, che mi avrebbe permesso di eliminare le parentesi c) sebbene l'articolo presenti argomenti validi contro l'adattamento delle proprietà, indipendentemente dal fatto che C ++ 'abbia o non abbia bisogno' delle proprietà è molto soggettivo. Il C ++ è equivalente a una macchina da turismo anche senza di loro, ma ciò non significa che avere un tale zucchero di sintassi renderebbe il C ++ più produttivo.
Radim Vansa

3
Assolutamente no.

Risposte:


87

In C ++ puoi scrivere le tue caratteristiche. Ecco un esempio di implementazione di proprietà che utilizzano classi senza nome. Articolo di 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;
};

Puoi scrivere i tuoi getter e setter sul posto e se desideri l'accesso ai membri della classe titolare puoi estendere questo codice di esempio.


1
Qualche idea su come modificare questo codice per avere ancora una variabile membro privata a cui Foo può accedere internamente, mentre l'API pubblica espone solo la proprietà? Ovviamente potrei semplicemente rendere Foo un amico di alpha / beta, ma dovrei comunque scrivere alpha.value, per accedere al valore, ma preferirei che l'accesso diretto alla variabile membro dall'interno di Foo sembrasse più simile accedere a un membro di Foo stesso e non a un membro di una speciale classe di proprietà annidata.
Kaiserludi

1
@Kaiserludi Sì: in questo caso, rendi privati ​​alpha e bravo. In Foo, puoi leggere / scrivere con quelle "proprietà" sopra, ma al di fuori di Foo, questo non sarebbe più possibile. Per aggirare questo problema, crea un riferimento const che sia pubblico. È accessibile dall'esterno, ma solo per la lettura poiché è un riferimento costante. L'unico avvertimento è che avrai bisogno di un altro nome per il riferimento const pubblico. Personalmente lo userei _alphaper la variabile privata e alphaper il riferimento.
Kapichu

1
@Kapichu: non una soluzione, per 2 motivi. 1) In C # le proprietà getter / setter vengono spesso utilizzate per incorporare controlli di sicurezza che vengono forzati agli utenti pubblici della classe consentendo al contempo alle funzioni membro di accedere direttamente al valore. 2) i riferimenti const non sono liberi: a seconda del compilatore / piattaforma si ingrandiranno sizeof(Foo).
ceztko

@psx: a causa dei limiti di questo approccio, lo eviterei e aspetterei un'adeguata aggiunta allo standard, se mai apparirà.
ceztko

@Kapichu: Ma alpha e bravo nel codice di esempio sono le proprietà. Vorrei accedere direttamente alla variabile stessa dall'interno dell'implementazione di Foo, senza la necessità di utilizzare una proprietà, mentre vorrei esporre solo l'accesso tramite una proprietà nell'API.
Kaiserludi

53

C ++ non ha questo integrato, puoi definire un modello per imitare la funzionalità delle proprietà:

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

Per definire una proprietà :

Property<float> x;

Per implementare un getter / setter personalizzato è sufficiente ereditare:

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;

Per definire una proprietà di sola lettura :

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

E per usarlo in classeOwner :

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

Potresti definire alcuni dei precedenti nelle macro per renderlo più conciso.


Sono curioso se questo compila fino a una funzionalità a costo zero, non so se il wrapping di ogni membro di dati in un'istanza di classe risulterà nello stesso tipo di impacchettamento della struttura, ad esempio.
Dai

1
La logica "getter / setter personalizzato" potrebbe essere resa sintatticamente più pulita mediante l'uso di funzioni lambda, sfortunatamente non puoi definire una lambda al di fuori di un contesto eseguibile in C ++ (ancora!) Quindi senza usare una macro del preprocessore ti ritroverai con codice che è solo complicato come getter / setter stupidi, il che è un peccato.
Dai

2
La "classe: ..." nell'ultimo esempio è interessante e manca negli altri esempi. Crea la necessaria dichiarazione di amicizia, senza introdurre un nuovo nome di classe.
Hans Olsson

Una grande differenza tra questa e la risposta 2010-Nov-19 è che ciò consente di ignorare il getter o il setter caso per caso. In questo modo si potrebbe controllare l'input per essere nel raggio d'azione, o per inviare una notifica di modifica per cambiare i listener di eventi, o un posto per bloccare un punto di interruzione.
Eljay

Si noti che probabilmente non virtualè necessario per la maggior parte dei casi d'uso, poiché è improbabile che una proprietà venga utilizzata in modo polimorfico.
Eljay

28

Non c'è niente nel linguaggio C ++ che funzionerà su tutte le piattaforme e compilatori.

Ma se sei disposto a rompere la compatibilità multipiattaforma e impegnarti con un compilatore specifico potresti essere in grado di utilizzare tale sintassi, ad esempio in Microsoft Visual C ++ puoi fare

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

Puoi emulare getter e setter in una certa misura avendo un membro di tipo dedicato e sovrascrivendo operator(type)e operator=per esso. Se sia una buona idea è un'altra domanda e vado alla +1risposta di Kerrek SB per esprimere la mia opinione al riguardo :)


È possibile emulare la chiamata a un metodo durante l'assegnazione o la lettura da tale tipo, ma non è possibile distinguere chi chiama l'operazione di assegnazione (per vietarla se non è il proprietario del campo) - cosa sto cercando di fare specificando un accesso diverso livello per getter e setter.
Radim Vansa

@ Flavius: basta aggiungere una friendal proprietario del campo.
kennytm

17

Magari dai un'occhiata alla classe di proprietà che ho assemblato nelle ultime ore: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Ti consente di avere proprietà che si comportano in questo modo:

CTestClass myClass = CTestClass();

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

Bello, tuttavia, sebbene questo consenta l'accesso definito dall'utente, non esiste la funzione getter / setter privato che stavo cercando.
Radim Vansa

17

Con C ++ 11 puoi definire un modello di classe Property e usarlo in questo modo:

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

Ed ecco il template della classe 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
cosa C::*significa? Non ho mai visto nulla di simile prima?
Rika

1
È un puntatore a una funzione membro non statica in classe C. È simile a un semplice puntatore a una funzione, ma per chiamare la funzione membro è necessario fornire un oggetto su cui viene chiamata la funzione. Ciò si ottiene con la linea itsObject->*itsSetter(theValue)nell'esempio sopra. Vedi qui per una descrizione più dettagliata di questa funzione.
Christoph Böhme

@ Niceman, c'è un esempio di utilizzo? Sembra essere molto costoso avere come membro. Non è particolarmente utile nemmeno come membro statico.
Grim Fandango

16

Come molti altri hanno già detto, non c'è supporto integrato nella lingua. Tuttavia, se stai prendendo di mira il compilatore Microsoft C ++, puoi sfruttare l'estensione specifica di Microsoft per le proprietà che è documentata qui.

Questo è l'esempio dalla pagina collegata:

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

No, C ++ non ha il concetto di proprietà. Sebbene possa essere scomodo definire e chiamare getThis () o setThat (value), stai dichiarando al consumatore di quei metodi che alcune funzionalità possono verificarsi. L'accesso ai campi in C ++, d'altra parte, indica al consumatore che non si verificherà alcuna funzionalità aggiuntiva o imprevista. Le proprietà renderebbero questo meno ovvio poiché l'accesso alla proprietà a prima vista sembra reagire come un campo, ma in realtà reagisce come un metodo.

Per inciso, stavo lavorando in un'applicazione .NET (un CMS molto noto) cercando di creare un sistema di appartenenza del cliente. A causa del modo in cui avevano utilizzato le proprietà per i loro oggetti utente, si stavano attivando azioni che non avevo previsto, facendo sì che le mie implementazioni venissero eseguite in modi bizzarri, inclusa la ricorsione infinita. Questo perché i loro oggetti utente hanno effettuato chiamate al livello di accesso ai dati o ad un sistema di memorizzazione nella cache globale durante il tentativo di accedere a cose semplici come StreetAddress. Il loro intero sistema era fondato su quello che chiamerei abuso di proprietà. Se avessero usato metodi anziché proprietà, penso che avrei capito cosa stava andando storto molto più rapidamente. Se avessero usato i campi (o almeno fatto che le loro proprietà si comportassero più come i campi), penso che il sistema sarebbe stato più facile da estendere e mantenere.

[Modifica] Ha cambiato i miei pensieri. Avevo avuto una brutta giornata e sono andato un po 'a farneticare. Questa pulizia dovrebbe essere più professionale.



4

Questa non è esattamente una proprietà, ma fa quello che vuoi in modo semplice:

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

Qui la grande X si comporta come public int X { get; private set; }nella sintassi C #. Se vuoi proprietà in piena regola, ho fatto un primo colpo per implementarle qui .


2
Questa non è una buona idea. Ogni volta che fai una copia di un oggetto di questa classe, il riferimento Xdel nuovo oggetto punterà ancora al membro del vecchio oggetto, perché viene copiato semplicemente come un membro del puntatore. Questo è un male di per sé, ma quando il vecchio oggetto viene eliminato, si ottiene un danneggiamento della memoria. Per fare in modo che funzioni, dovresti anche implementare il tuo costruttore di copia, operatore di assegnazione e costruttore di spostamento.
toster

4

Probabilmente lo sai, ma vorrei semplicemente fare quanto segue:

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

Questo approccio è semplice, non utilizza trucchi intelligenti e porta a termine il lavoro!

Il problema però è che ad alcune persone non piace anteporre ai loro campi privati ​​un carattere di sottolineatura e quindi non possono davvero utilizzare questo approccio, ma fortunatamente per coloro che lo fanno, è davvero semplice. :)

I prefissi get e set non aggiungono chiarezza alla tua API ma li rendono più dettagliati e il motivo per cui non penso aggiungano informazioni utili è perché quando qualcuno ha bisogno di usare un'API se l'API ha senso, probabilmente si renderà conto di cosa si tratta fa a meno dei prefissi.

Un'altra cosa, è facile capire che queste sono proprietà perché namenon è un verbo.

Nel peggiore dei casi, se le API sono coerenti e la persona non si è resa conto che name()è un accessorio ed name(value)è un mutatore, dovrà cercarlo solo una volta nella documentazione per comprendere il modello.

Per quanto io ami C #, non credo che il C ++ abbia bisogno di proprietà!


I tuoi mutatori hanno senso se uno usa foo(bar)(invece del più lento foo = bar), ma i tuoi accessori non hanno nulla a che fare con le proprietà ...
Matthias

@ Matthias Affermare che non ha nulla a che fare con le proprietà, non mi dice niente, puoi approfondire? inoltre non ho provato a confrontarli ma se hai bisogno di un mutatore e di un accessorio puoi usare questa convenzione.
Eyal Solnik

La domanda riguarda il concetto di software delle proprietà. Le proprietà possono essere utilizzate come se fossero membri di dati pubblici (utilizzo), ma in realtà sono metodi speciali chiamati accessors (dichiarazione). La tua soluzione si attiene ai metodi (getter / setter ordinari) per la dichiarazione e l'utilizzo. Quindi questo è, prima di tutto, sicuramente non l'uso richiesto dall'OP, ma piuttosto una convenzione di denominazione strana e non convenzionale (e quindi, in secondo luogo, anche nessuno zucchero sintattico).
Matthias

Come effetto collaterale minore, il tuo mutatore funziona sorprendentemente come una proprietà, poiché in C ++ si inizializza meglio con foo(bar)invece foo=barche può essere ottenuto con un void foo(Bar bar)metodo mutatore su una _foovariabile membro.
Matthias

@ Matthias so cosa sono le proprietà, ho scritto C ++ un bel po 'e C # per oltre un decennio, non sto discutendo sui vantaggi delle proprietà e su cosa sono ma non ne ho mai VERAMENTE bisogno in C ++, infatti tu Stai dicendo che possono essere usati come dati pubblici e questo è per lo più vero, ma ci sono casi in C # in cui non puoi nemmeno usare le proprietà direttamente, come passare una proprietà per ref mentre con un campo pubblico, puoi.
Eyal Solnik

4

No .. Ma dovresti considerare se è solo la funzione get: set e nessun compito aggiuntivo preformato all'interno dei metodi get: set lo rendono pubblico.


2

Ho raccolto le idee da più fonti C ++ e le ho inserite in un bell'esempio ancora abbastanza semplice per getter / setter in 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;
}

Produzione:

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

Puoi testarlo online qui: http://codepad.org/zosxqjTX


C'è un sovraccarico di memoria per mantenere l'auto-rifrazione, + una sintassi scomoda del ctor.
Red.Wave

Proporre immobili? Immagino che ci siano state molte proposte rifiutate.
Red.Wave

@ Red.Wave Allora inchinati al signore e maestro del rifiuto. Benvenuto in C ++. Clang e MSVC hanno estensioni personalizzate per le proprietà, se non vuoi gli auto-riferimenti.
lama12345

Non posso mai inchinarmi. Non tutte le funzionalità sono nemmeno appropriate, credo. Per me gli oggetti sono molto più di una coppia di funzioni setter + getter. Ho provato le proprie implementazioni evitando il sovraccarico di memoria permanente non necessario, ma la sintassi e il lavoro di dichiarazione delle istanze non erano soddisfacenti; Sono stato attratto dall'uso di macro dichiarative, ma non sono comunque un grande fan delle macro. E il mio approccio alla fine ha portato a proprietà a cui si accede con la sintassi delle funzioni, che molti, me compreso, non approvano.
Red.Wave

0

La tua classe ha davvero bisogno di applicare qualche invariante o è solo un raggruppamento logico di elementi membri? Se è quest'ultimo dovresti considerare di rendere la cosa una struttura e di accedere direttamente ai membri.


0

Ci sono una serie di macro scritte qui . Questo ha dichiarazioni di proprietà convenienti per tipi di valore, tipi di riferimento, tipi di sola lettura, tipi forti e deboli.

class MyClass {

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

 public:
 ...

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.