Pregunta

He estado programando en C # por un tiempo y ahora quiero poner al día mis conocimientos de C ++.

Tener la clase:

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

¿Cuál sería el mejor enfoque (sólo quiero permitir el acceso de lectura al campo name_):

  • utilizar un método getter: inline const std::string& name() const { return name_; }
  • hacer público el campo ya que es una constante

Gracias.

¿Fue útil?

Solución

Se tiende a ser una mala idea para hacer no constante campos públicos, ya que entonces se convierte en difícil obligar a las restricciones de comprobación de errores y / o añadir efectos secundarios a los cambios de valor en el futuro.

En su caso, tiene un campo constante, por lo que las cuestiones anteriores no son un problema. La desventaja principal de lo que es un campo público es que estás de bloqueo hacia abajo la implementación subyacente. Por ejemplo, si en el futuro se quisiera cambiar la representación interna de un C-cuerda o una cadena Unicode, o algo más, entonces sería romper todos los códigos de cliente. Con un captador, podría convertir a la representación legado para los clientes existentes al tiempo que proporciona la nueva funcionalidad a los nuevos usuarios a través de un nuevo comprador.

Me todavía sugiero tener un método de obtención como el que se ha colocado por encima. Esto maximizará la flexibilidad de su futuro.

Otros consejos

El uso de un método de obtención es una mejor opción de diseño para una clase de larga duración, ya que le permite reemplazar el método getter con algo más complicado en el futuro. Aunque esto parece menos probable que se necesiten para un valor constante, el costo es bajo y los posibles beneficios son grandes.

Como acotación al margen, en C ++, es una idea especialmente buena para dar tanto el getter y setter de un miembro de el mismo nombre , ya que en el futuro se puede entonces cambiar realmente el que el par de métodos :

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

en una sola variable, público miembro que define una operator()() para cada uno:

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

necesitará el código de cliente para volver a compilar, por supuesto, pero no se requieren cambios de sintaxis! Obviamente, esta transformación funciona igual de bien para valores const, en los que hace falta un único un captador.

Como un aparte, en C ++, es algo extraño tener un miembro de referencia const. Tiene que asignar en la lista constructor. De quién es el hecho de que la memoria del objeto y lo que es su vida?

En cuanto a estilo, estoy de acuerdo con los otros que no desea exponer sus partes privadas. :-) Me gusta este patrón para setters / captadores

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 esta manera usted puede hacer algo como:

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

Creo que el enfoque de C ++ 11 sería más como esto ahora.

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

He probado esto en Visual Studio 2013. Por desgracia, con el fin de utilizar el almacenamiento subyacente en el interior del LambdaSetter que necesitaba para proporcionar una "GetRawData" descriptor de acceso público que puede conducir a la encapsulación roto, pero se puede o bien dejarlo fuera y proporcionar su propia contenedor de almacenamiento para T o simplemente asegurar que la única vez que utilice "GetRawData" es cuando se está escribiendo un método de obtención de encargo / definidor.

A pesar de que el nombre es inmutable, es posible que aún quieren tener la opción de calcular que en lugar de almacenarla en un campo. (Me di cuenta que es poco probable que "nombre", pero vamos a apuntar para el caso general.) Por esa razón, incluso los campos constantes se envuelven mejor dentro de captadores:

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

Tenga en cuenta que si tuviera que cambiar getName() para devolver un valor calculado, no podría volver ref const. Eso está bien, ya que no se requiere ningún cambio en las personas que llaman (recompilación de módulo.)

Evitar las variables públicas, a excepción de las clases que son esencialmente estructuras de tipo C. No es sólo una buena práctica para entrar.

Una vez que haya definido la interfaz de la clase, que nunca podría ser capaz de cambiarlo (que no sea añadiendo a ella), porque la gente va a construir en él y confiar en él. Realización de una variable pública significa que es necesario tener esa variable, y hay que asegurarse de que tiene lo que necesita el usuario.

Ahora, si se utiliza un captador, que está prometiendo proporcionar alguna información, que en la actualidad se mantiene en esa variable. Si la situación cambia, y prefiere no mantener esa variable todo el tiempo, puede cambiar el acceso. Si los requisitos de cambio (y he visto algunos cambios en los requisitos bastante impares), y que en su mayoría tienen el nombre que está en esta variable, pero a veces el que está en esa variable, sólo puede cambiar el captador. Si ha realizado la variable pública, que estaría pegado con él.

Esto no siempre va a pasar, pero me resulta mucho más fácil sólo para escribir un captador rápida de analizar la situación para ver si me arrepiento de hacer pública la variable (y el riesgo de equivocarse más adelante).

Hacer variables miembro privado es un buen hábito para entrar. Cualquier tienda que tiene los estándares del código es probablemente va a prohibir a hacer pública la variable miembro de vez en cuando, y cualquier tienda con las revisiones de código es probable que le critican por ello.

Siempre que realmente no importa para facilitar la escritura, entrar en el hábito más seguro.

Ideas de recogida en múltiples fuentes de C ++ y ponerlo en un buen ejemplo, sigue siendo bastante simple para 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;
}

Salida:

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

Puede probarlo en línea aquí: http://codepad.org/zosxqjTX

PS: FO Yvette <3

A partir de la teoría de los patrones de diseño; "Encapsular lo que varía". Mediante la definición de un 'captador' hay una buena adhesión al principio anteriormente. Por lo tanto, si la aplicación de representación de los cambios de miembros en el futuro, el miembro puede ser 'masajea' antes de volver de la 'captador'; que no implica ningún código de refactorización en el lado del cliente, donde se realiza la llamada 'captador'.

Saludos,

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top