Domanda

Sono stato di programmazione in C # per un po 'e ora voglio rispolverare le mie capacità di C ++.

Avere la classe:

class Foo
{
    const std::string& name_;
    ...
};

Quale sarebbe l'approccio migliore (voglio solo per consentire l'accesso in lettura al campo name_):

  • utilizzare un metodo getter: inline const std::string& name() const { return name_; }
  • rendere pubblico il campo dal momento che è una costante

Grazie.

È stato utile?

Soluzione

Si tende ad essere una cattiva idea di fare non const campi pubblici perché diventa poi difficile forzare errore vincoli di controllo e / o aggiungere effetti collaterali alle variazioni del valore in futuro.

Nel tuo caso, si dispone di un campo const, in modo che le questioni di cui sopra non sono un problema. L'aspetto negativo principale che lo rende un campo pubblico è che si sta bloccando l'implementazione sottostante. Ad esempio, se in futuro si volesse cambiare la rappresentazione interna di un C-stringa o una stringa Unicode, o qualcosa d'altro, allora si sarebbe rompere tutto il codice del client. Con un getter, si potrebbe convertire la rappresentazione legacy per i clienti esistenti, fornendo la funzionalità più recente per i nuovi utenti attraverso un nuovo getter.

Mi piacerebbe ancora suggeriamo di avere un metodo getter come quello che si è posto al di sopra. Questo ci permetterà di ottimizzare la flessibilità futuro.

Altri suggerimenti

utilizzando un metodo getter è una scelta migliore progettazione per una classe di lunga durata in quanto consente di sostituire il metodo getter con qualcosa di più complicato in futuro. Anche se questo sembra meno probabile che siano necessarie per un valore const, il costo è basso ed i possibili benefici sono grandi.

Per inciso, in C ++, è un'idea particolarmente buona per dare sia il getter e setter per un membro di lo stesso nome , dal momento che in futuro si può quindi effettivamente cambiare la coppia di metodi :

class Foo {
public:
    std::string const& name() const;          // Getter
    void name(std::string const& newName);    // Setter
    ...
};

In una singola variabile, pubblica dell'utente che definisce un operator()() per ogni:

// This class encapsulates a fancier type of name
class fancy_name {
public:
    // Getter
    std::string const& operator()() const {
        return _compute_fancy_name();    // Does some internal work
    }

    // Setter
    void operator()(std::string const& newName) {
        _set_fancy_name(newName);        // Does some internal work
    }
    ...
};

class Foo {
public:
    fancy_name name;
    ...
};

Il codice cliente dovrà essere ricompilato, naturalmente, ma sono tenuti sintassi non cambia! Ovviamente, questa trasformazione funziona altrettanto bene per valori const, in cui è necessario solo un getter.

Per inciso, in C ++, è piuttosto strano avere un organo di riferimento const. È necessario assegnare nella lista del costruttore. Chi possiede la realtà ricordo di quell'oggetto e ciò che è la sua vita?

Per quanto riguarda lo stile, sono d'accordo con gli altri che non si desidera esporre le vostre parti intime. :-) Mi piace questo modello per setter / getter

class Foo
{
public:
  const string& FirstName() const;
  Foo& FirstName(const string& newFirstName);

  const string& LastName() const;
  Foo& LastName(const string& newLastName);

  const string& Title() const;
  Foo& Title(const string& newTitle);
};

In questo modo si può fare qualcosa di simile:

Foo f;
f.FirstName("Jim").LastName("Bob").Title("Programmer");

Credo che l'approccio C ++ 11 sarebbe più come adesso.

#include <string>
#include <iostream>
#include <functional>

template<typename T>
class LambdaSetter {
public:
    LambdaSetter() :
        getter([&]() -> T { return m_value; }),
        setter([&](T value) { m_value = value; }),
        m_value()
    {}

    T operator()() { return getter(); }
    void operator()(T value) { setter(value); }

    LambdaSetter operator=(T rhs)
    {
        setter(rhs);
        return *this;
    }

    T operator=(LambdaSetter rhs)
    {
        return rhs.getter();
    }

    operator T()
    { 
        return getter();
    }


    void SetGetter(std::function<T()> func) { getter = func; }
    void SetSetter(std::function<void(T)> func) { setter = func; }

    T& GetRawData() { return m_value; }

private:
    T m_value;
    std::function<const T()> getter;
    std::function<void(T)> setter;

    template <typename TT>
    friend std::ostream & operator<<(std::ostream &os, const LambdaSetter<TT>& p);

    template <typename TT>
    friend std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p);
};

template <typename T>
std::ostream & operator<<(std::ostream &os, const LambdaSetter<T>& p)
{
    os << p.getter();
    return os;
}

template <typename TT>
std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p)
{
    TT value;
    is >> value;
    p.setter(value);
    return is;
}


class foo {
public:
    foo()
    {
        myString.SetGetter([&]() -> std::string { 
            myString.GetRawData() = "Hello";
            return myString.GetRawData();
        });
        myString2.SetSetter([&](std::string value) -> void { 
            myString2.GetRawData() = (value + "!"); 
        });
    }


    LambdaSetter<std::string> myString;
    LambdaSetter<std::string> myString2;
};

int _tmain(int argc, _TCHAR* argv[])
{
    foo f;
    std::string hi = f.myString;

    f.myString2 = "world";

    std::cout << hi << " " << f.myString2 << std::endl;

    std::cin >> f.myString2;

    std::cout << hi << " " << f.myString2 << std::endl;

    return 0;
}

Ho provato questo in Visual Studio 2013. Purtroppo, al fine di utilizzare l'archiviazione sottostante all'interno del LambdaSetter avevo bisogno di fornire una "GetRawData" di accesso pubblico che può portare a incapsulamento rotto, ma è possibile lasciare fuori e fornire il proprio contenitore di stoccaggio per T o semplicemente assicurarsi che l'unica volta che si utilizza "GetRawData" è quando si sta scrivendo un / metodo setter getter personalizzato.

Anche se il nome è immutabile, si può ancora voglia di avere la possibilità di calcolo piuttosto che riporlo in un campo. (Mi rendo conto che questo è improbabile per "nome", ma cerchiamo di obiettivo per il caso generale). Per questo motivo, anche i campi costanti sono più avvolti all'interno di getter:

class Foo {
    public:
        const std::string& getName() const {return name_;}
    private:
        const std::string& name_;
};

Si noti che se si dovesse cambiare getName() per restituire un valore calcolato, non poteva tornare ref const. Va bene, perché non richiede alcuna modifica ai chiamanti (ricompilazione modulo.)

Evitare variabili pubbliche, fatta eccezione per le classi che sono essenzialmente le strutture in stile C. Non è solo una buona pratica per entrare.

Dopo aver definito l'interfaccia di classe, non si potrebbe mai essere in grado di cambiarlo (altro che aggiungere ad esso), perché la gente si baserà su di esso e fare affidamento su di esso. Fare un pubblico variabile significa che è necessario disporre di quella variabile, ed è necessario assicurarsi che ha ciò che l'utente ha bisogno.

Ora, se si utilizza un getter, si sta promettendo di fornire alcune informazioni, che è attualmente conservato in quella variabile. Se la situazione cambia, e si preferisce non mantenere quella variabile per tutto il tempo, è possibile modificare l'accesso. Se le esigenze cambiano (e ho visto alcuni requisiti piuttosto strane variazioni), e per lo più devono disporre del nome che è in questa variabile, ma a volte quella di quella variabile, si può semplicemente cambiare il getter. Se hai fatto il pubblico variabile, devi essere bloccato con esso.

Questo non accadrà sempre, ma trovo molto più facile solo per scrivere la rapida getter rispetto ad analizzare la situazione per vedere se avessi dispiace rendendo il pubblico variabili (e il rischio di sbagliare in seguito).

Fare le variabili membro privato è una buona abitudine per entrare. Ogni negozio che ha norme del codice è probabilmente andando a vietare rendere pubblico variabile occasionale membro e qualsiasi negozio con le revisioni del codice è probabile che a criticare per questo.

Ogni volta che davvero non importa per la facilità di scrittura, prendere l'abitudine più sicuro.

idee raccolte da più fonti di C ++ e metterlo in una bella, ancora abbastanza semplice esempio 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;
}

Output:

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

È possibile verificare on-line qui: http://codepad.org/zosxqjTX

PS: FO Yvette <3

Dalla teoria Design Patterns; "Incapsulare ciò che varia". Definendo un 'getter' c'è buona aderenza al principio sopra. Quindi, se l'implementazione-rappresentazione delle variazioni membri, in futuro, il membro può essere 'massaggiato' prima di ritornare dal 'getter'; il che implica alcun codice refactoring sul lato client in cui viene effettuata la chiamata 'getter'.

Saluti,

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top