2010-04-01 11 views
11

Come faccio ad avere proprietà in classe C++, come in una classe C#.Avere proprietà pubbliche in classe C++

Non voglio avere metodi getter e setter.

+2

Dai un'occhiata a una domanda simile qui: http://stackoverflow.com/questions/2017623/forward-unbreakable-accessor-class-templates-c. Abbiamo messo a punto alcuni modi di implementare le proprietà usando le funzionalità di C++. –

+0

C++ specifico della piattaforma o indipendente dalla piattaforma? In Windows, il complemento VC++ ha estensioni non standard che lo supportano. Vedi la mia risposta qui sotto. –

risposta

11

È possibile utilizzare una soluzione simile a quella suggerita da Jon, mantenendo tuttavia la semantica C++ ordinaria utilizzando l'overloading dell'operatore. Ho leggermente modificato il codice di Jon come segue (spiegazioni seguono il codice):

#include <iostream> 

template<typename T> 
class Accessor { 
public: 
    explicit Accessor(const T& data) : value(data) {} 

    Accessor& operator=(const T& data) { value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { this->value = other.value; return *this; } 
    operator T() const { return value; } 
    operator T&() { return value; } 

private: 
    Accessor(const Accessor&); 


    T value; 

}; 

struct Point { 
    Point(int a = 0, int b = 0) : x(a), y(b) {} 
    Accessor<int> x; 
    Accessor<int> y; 
}; 

int main() { 
    Point p; 
    p.x = 10; 
    p.y = 20; 
    p.x++; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

Abbiamo sovraccaricare operator= di mantenere la solita sintassi assegnazione invece di una sintassi funzione di chiamata-like. Usiamo l'operatore del cast come un "getter". Abbiamo bisogno della seconda versione di operator= per consentire l'assegnazione del secondo tipo in main().

Ora è possibile aggiungere ai puntatori di funzione del costruttore di Accessor, o meglio - ai funtori - chiamare come getter/setter in qualsiasi modo vi sembra giusto. Il seguente esempio presuppone che il setter bool ritorno della funzione di trasmettere un accordo per l'impostazione del nuovo valore, e il getter può semplicemente modificarlo su di essa la via d'uscita:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) 
    { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator()(const T& data) 
    { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { value = getter(value); return value;} 
    operator T&() { value = getter(value); return value; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

Tuttavia, come l'ultima linea dimostra che ha un bug. L'operatore del cast che restituisce un T & consente agli utenti di ignorare il setter, poiché consente l'accesso al valore privato. Un modo per risolvere questo bug è quello di implementare tutti gli operatori che vuoi fornire da Accessor.Ad esempio, nel seguente codice che ho usato l'operatore + =, e dal momento che ho rimosso il riferimento operatore di cast ritorno ho dovuto implementare un operator+=:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) const { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator() (const T& data) const { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) const { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) const { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
private: 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { return getter(value);} 

    Accessor& operator+=(const T& data) { if (setter(value+data)) value += data; return *this; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

Dovrete implementa tutti gli operatori si sta andando usare.

+0

+1 per picchiarmi al punch, ma non mi piace l'idea di fornire tonnellate di sovraccarichi dell'operatore come quello. Un altro modo per prendersi cura dei valori eventualmente invalidati dall'esterno è offrire un diverso callback "è stato scritto" che viene richiamato quando il valore viene letto successivamente. –

+0

Considerando che definisci tutti quegli operatori solo una volta nella classe template, non penso che sia così terribile. Il codice STL o boost è orribile se paragonato a questo (e generalmente parlando :)) secondo me. Ma ci sono anche altre opzioni. Bisogna considerare le proprie circostanze e fare di conseguenza la propria scelta. :) – conio

1

Non è così. C++ non supporta proprietà come C#. Se vuoi che il codice funzioni su set/get, dovrà essere un metodo.

+0

Anche le proprietà eseguono realmente i metodi in C#. Il compilatore lo nasconde da te. –

7

Per un comportamento simile a questo, utilizzo un meta-accesso basato sui modelli. Ecco un uno molto semplificato per i tipi di POD:

template<class T> 
struct accessor { 

    explicit accessor(const T& data) : value(data) {} 
    T operator()() const { return value; } 
    T& operator()() { return value; } 
    void operator()(const T& data) { value = data; } 

private: 

    accessor(const accessor&); 
    accessor& operator=(const accessor&); 
    T value; 

}; 

uso tipico è come questo:

struct point { 
    point(int a = 0, int b = 0) : x(a), y(b) {} 
    accessor<int> x; 
    accessor<int> y; 
}; 

point p; 
p.x(10); 
p.y(20); 
p.x()++; 
std::cout << p.x(); 

Il compilatore inline solito queste chiamate se impostare le cose a destra e hanno ottimizzazione acceso. Non si tratta più di un collo di bottiglia per le prestazioni rispetto all'utilizzo di getter e setter effettivi, indipendentemente dall'ottimizzazione. È semplice estenderlo per supportare automaticamente i tipi non POD o enumerati o per consentire la registrazione dei callback ogni volta che i dati vengono letti o scritti.

Modifica: Se si preferisce non utilizzare le parentesi, è sempre possibile definire operator=() e un operatore di trasmissione implicita. Ecco una versione che fa proprio questo, aggiungendo anche il supporto base di callback "roba successo":

Ulteriori modifica: Ok, mi sono perso totalmente qualcuno che ha già realizzato una versione rivista del mio codice. Sospiro.

+0

Perché stai usando uno stile fasullo clunky quando potresti avere definito operator =? –

+0

@ Ben: aggiunte differenze per completezza. Mi è sfuggito il modo in cui quella merda sembra in C#. –

1

Proprietà non sono supportati in C++, ma è possibile implementare:
1) Utilizzando modelli
2) Per rendere estensione lingua e la scrittura di codice personalizzato preprocessore

Entrambi gli approcci non sarà facile, ma può essere fatto.

+0

C++ supporta l'operatore di overload =, quindi non sono richieste estensioni. –

+0

@Ben Voigt Dipende da cosa esattamente si desidera implementare e da quante proprietà si desidera. Con una grande quantità di codice, introdurre una parola chiave o due e scrivere il preprocessore del codice sarà un'idea migliore: renderà il codice più leggibile. – SigTerm

3

Ecco un'implementazione PoC Ho fatto un po 'di tempo fa, funziona bene eccetto che è necessario impostare qualcosa nel costruttore per farlo funzionare bene e senza intoppi.

http://www.codef00.com/code/Property.h

Ecco l'utilizzo esempio:

#include <iostream> 
#include "Property.h" 


class TestClass { 
public: 
    // make sure to initialize the properties with pointers to the object 
    // which owns the property 
    TestClass() : m_Prop1(0), m_Prop3(0.5), prop1(this), prop2(this), prop3(this) { 
    } 

private: 
    int getProp1() const { 
     return m_Prop1; 
    } 

    void setProp1(int value) { 
     m_Prop1 = value; 
    } 

    int getProp2() const { 
     return 1234; 
    } 

    void setProp3(double value) { 
     m_Prop3 = value; 
    } 

    int m_Prop1; 
    double m_Prop3; 

public: 
    PropertyRW<int, TestClass, &TestClass::getProp1, &TestClass::setProp1> prop1; 
    PropertyRO<int, TestClass, &TestClass::getProp2> prop2; 
    PropertyWO<double, TestClass, &TestClass::setProp3> prop3; 
}; 

e alcuni utilizzo di questa classe ...

int main() { 
    unsigned int a; 
    TestClass t; 
    t.prop1 = 10; 
    a = t.prop1; 
    t.prop3 = 5; 
    a = t.prop2; 
    std::cout << a << std::endl; 
    return 0; 
} 

Ci sono due fastidi con questo approccio:

  1. Hai bisogno di g la proprietà è un puntatore alla sua classe proprietaria.
  2. La sintassi per dichiarare una proprietà è un po 'prolisso, ma scommetto che posso pulire che un po' con alcune macro
+0

Un po 'imbarazzante, sì, ma l'idea di registrare l'accessore con 'this' è buona, dal momento che consente facilmente alle proprietà di giocare brutti scherzi con chi può accedervi in ​​base all'istanza e digitare le informazioni. –

1

si potrebbe fornire metodi get e set che hanno nomi simili ai membri di dati :

class Example 
{ 
    private: 
    unsigned int x_; 
    double d_; 
    std::string s_s; 
    public: 
    unsigned int x(void) const 
    { return x_;} 

    void x(unsigned int new_value) 
    { x_ = new_value;} 

    double d(void) const 
    { return d_;} 
    void d(double new_value) 
    { d_ = new_value;} 

    const std::string& s(void) const 
    { return s_;} 
    void s(const std::string& new_value) 
    { s_ = new_value;} 
}; 

Anche se questo si avvicina, in quanto richiede l'utilizzo di '()' per ogni membro, non soddisfa l'esatta funzionalità di proprietà che Microsoft linguaggi forniscono.

La corrispondenza più simile per le proprietà è dichiarare i membri dei dati come pubblici.

+0

Errato, sovraccaricando l'operatore = è possibile ottenere la sintassi esatta (per l'utente) e le funzionalità di qualsiasi linguaggio che fornisce proprietà per l'utente. –

3

Se non ti interessa che il tuo codice C++ non venga compilato con qualcosa di diverso dal compilatore Microsoft Visual C++, puoi utilizzare alcune estensioni non standard del compilatore.

Ad esempio, il codice seguente creerà una proprietà di tipo C# denominata MyProperty.

struct MyType 
{ 
    // This function pair may be private (for clean encapsulation) 
    int get_number() const { return m_number; } 
    void set_number(int number) { m_number = number; } 

    __declspec(property(get=get_number, put=set_number)) int MyProperty; 
private: 
    int m_number: 
} 

int main() 
{ 
    MyType m; 
    m.MyProperty = 100; 
    return m.MyProperty; 
} 

Ulteriori informazioni su questa estensione di lingua Microsoft sono disponibili here.

Problemi correlati