Question

Je suis la programmation en C # pendant un certain temps et maintenant je veux rafraîchir mes compétences en C ++.

Avoir la classe:

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

Quelle serait la meilleure approche (je veux seulement permettre un accès en lecture au champ name_):

  • utiliser une méthode de lecture: inline const std::string& name() const { return name_; }
  • le public sur le terrain car il est une constante

Merci.

Était-ce utile?

La solution

Il a tendance à être une mauvaise idée de faire non const champs publics, car il devient alors difficile de forcer les contraintes de contrôle d'erreur et / ou ajouter des effets secondaires aux changements de valeur dans l'avenir.

Dans votre cas, vous avez un champ const, de sorte que les questions ci-dessus ne sont pas un problème. Le principal inconvénient d'en faire un domaine public est que vous verrouiller la mise en œuvre sous-jacente. Par exemple, si, dans l'avenir, vous vouliez changer la représentation interne à une chaîne C ou une chaîne Unicode, ou autre chose, alors vous cassez tout le code client. Avec un getter, vous pouvez convertir à la représentation de l'héritage pour les clients existants tout en offrant la fonctionnalité plus récente pour les nouveaux utilisateurs via un nouveau getter.

Je suggère toujours avoir une méthode de lecture comme celui que vous avez placé au-dessus. Cela permettra de maximiser votre future flexibilité.

Autres conseils

En utilisant une méthode getter est un meilleur choix de conception pour une classe de longue durée, car il vous permet de remplacer la méthode getter avec quelque chose de plus compliqué à l'avenir. Bien que cela semble moins susceptible d'être nécessaire pour une valeur const, le coût est faible et les avantages possibles sont grandes.

En aparté, en C ++, il est une idée particulièrement bonne pour donner à la fois le getter et setter pour un membre le même nom , car dans l'avenir, vous pouvez alors changer en fait la paire de méthodes :

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

en une seule variable membre du public qui définit un operator()() pour chacun:

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

Le code client devra être recompilé bien sûr, mais aucun changement de syntaxe sont nécessaires! De toute évidence, cette transformation fonctionne aussi bien pour les valeurs const, dans lequel seul un getter est nécessaire.

Soit dit en passant, en C ++, il est assez étrange d'avoir un élément de référence const. Vous devez lui attribuer dans la liste constructeur. Qui est propriétaire de la place en mémoire de cet objet et ce qui est il est vivant?

En ce qui concerne le style, je suis d'accord avec les autres que vous ne voulez pas exposer vos soldats. :-) J'aime ce modèle pour setters / getters

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

De cette façon, vous pouvez faire quelque chose comme:

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

Je pense que l'approche 11 C ++ serait plus comme ça maintenant.

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

J'ai testé cela dans Visual Studio 2013. Malheureusement, afin d'utiliser le stockage sous-jacent à l'intérieur du LambdaSetter je avais besoin de fournir un accesseur public « GetRawData » qui peut conduire à l'encapsulation brisée, mais vous pouvez soit laisser sortir et fournir votre propre conteneur de stockage T ou tout simplement faire en sorte que la seule fois que vous utilisez « GetRawData » est quand vous écrivez une méthode de lecture personnalisée / setter.

Même si le nom est immuable, vous pouvez toujours avoir la possibilité de calcul plutôt que de le stocker dans un champ. (Je sais que cela est peu probable pour le « nom », mais Visons pour le cas général.) Pour cette raison, même les champs constants sont mieux emballés dans des getters:

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

Notez que si vous deviez changer getName() pour une valeur calculée, il ne pouvait pas retourner ref const. C'est ok, car il ne nécessite aucune modification des appelants (modulo recompilation.)

Évitez les variables publiques, sauf pour les classes qui sont essentiellement struct C-style. Il est tout simplement pas une bonne pratique pour entrer dans.

Une fois que vous avez défini l'interface de classe, vous pourriez ne jamais être en mesure de le changer (autre que d'ajouter à elle), parce que les gens vont en tirer parti et compter sur elle. Faire un moyen public variable que vous devez avoir cette variable, et vous devez vous assurer qu'il a ce que l'utilisateur a besoin.

Maintenant, si vous utilisez un getter, vous promettant de fournir des informations, qui est actuellement maintenue dans cette variable. Si la situation change, et vous feriez maintenir plutôt pas cette variable tout le temps, vous pouvez modifier l'accès. Si les conditions changent (et j'ai vu des exigences assez bizarres changements), et vous avez besoin la plupart du temps le nom qui est dans cette variable, mais parfois celle de cette variable, vous pouvez simplement changer le getter. Si vous avez fait le public variable, vous seriez coincé avec elle.

Ce ne sera pas toujours le cas, mais je trouve beaucoup plus facile juste d'écrire un getter rapide que d'analyser la situation pour voir si je regretterais rendre public variable (et le risque d'être mal plus tard).

Rendre les variables membres privées est une bonne habitude à prendre. Tout magasin qui a des normes de code va probablement interdire rendre public variable membre occasionnel, et tout magasin avec des revues de code est susceptible de vous critiquer pour cela.

Chaque fois qu'il n'a pas d'importance pour la facilité d'écriture, entrer dans la plus sûre habitude.

idées recueillies à partir de plusieurs sources de C ++ et le mettre dans un joli exemple encore assez simple pour les getters / setters en 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;
}

Sortie:

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

Vous pouvez le tester en ligne ici: http://codepad.org/zosxqjTX

PS: FO Yvette <3

De la théorie des modèles de conception; « Symboliseront ce que varie ». En définissant un « getter » il y a une bonne adhérence au principe ci-dessus. Donc, si la mise en œuvre représentation des changements de membres à l'avenir, le membre peut être « massé » avant le retour de la « getter »; ce qui implique pas de code refactoring du côté client où l'appel « getter » est fait.

Cordialement,

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top