Pergunta

Eu tenho sido programação em C # por um tempo e agora eu quero escovar acima em minhas habilidades de C ++.

Ter a classe:

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

Qual seria a melhor abordagem (Eu só quero para permitir o acesso de leitura para o campo name_):

  • usar um método getter: inline const std::string& name() const { return name_; }
  • fazer o público campo, já que é uma constante

Graças.

Foi útil?

Solução

Ela tende a ser uma má idéia para fazer campos de público não-const, porque então se torna difícil de erro vigor verificando constrangimentos e / ou adicionar efeitos colaterais para alterações de valor no futuro.

No seu caso, você tem um campo const, então as questões acima não são um problema. A principal desvantagem de torná-lo um campo público é que você está bloqueando a implementação subjacente. Por exemplo, se no futuro você queria mudar a representação interna para um C-corda ou uma seqüência de caracteres Unicode, ou qualquer outra coisa, então você iria quebrar todo o código do cliente. Com um getter, você poderia converter a representação legado para os clientes existentes, proporcionando a funcionalidade mais recente de novos usuários através de um novo getter.

Eu ainda sugiro ter um método getter como o que você colocou acima. Isto irá maximizar a sua flexibilidade futuro.

Outras dicas

Usando um método getter é uma escolha melhor design para uma classe de vida longa, pois permite que você substitua o método getter com algo mais complicado no futuro. Embora este parece menos provável que seja necessário para um valor const, o custo é baixo e os possíveis benefícios são grandes.

Como um aparte, em C ++, é uma idéia especialmente boa para dar tanto o getter e setter para um membro do o mesmo nome , uma vez que no futuro você pode realmente mudar a par de métodos :

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

em uma única variável, membro público que define uma operator()() para cada:

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

O código do cliente precisarão ser recompilados é claro, mas nenhumas mudanças da sintaxe são obrigatórios! Obviamente, esta transformação funciona tão bem para valores const, nos quais é apenas necessário um getter.

Como um aparte, em C ++, que é um pouco estranho ter um membro const referência. Você tem que atribuí-lo na lista construtor. Quem é dono da verdade, a memória daquele objeto e qual é a sua vida?

Quanto ao estilo, eu concordo com os outros que você não deseja expor suas partes íntimas. :-) I como este padrão para 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);
};

Desta forma, você pode fazer algo como:

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

Eu acho que o C ++ 11 abordagem seria mais parecido com isso agora.

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

Eu testei isso no Visual Studio 2013. Infelizmente, a fim de usar o armazenamento subjacente dentro do LambdaSetter eu precisava para fornecer um "GetRawData" acessor público que pode levar à quebrado encapsulamento, mas você pode deixá-lo fora e fornecer seu próprio recipiente de armazenamento para T ou apenas garantir que a única vez que você usa "GetRawData" é quando você está escrevendo um método getter custom / setter.

Mesmo que o nome é imutável, você ainda pode querer ter a opção de computação, em vez de armazená-lo em um campo. (Sei que isso é improvável para "nome", mas vamos apontar para o caso geral.) Por esse motivo, até mesmo campos constantes são os melhores dentro envolto de getters:

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

Note que, se você fosse para a mudança getName() para retornar um valor calculado, não poderia voltar ref const. Isso é ok, porque não vai exigir qualquer alteração aos chamadores (modulo recompilação.)

Evite variáveis ??públicas, exceto para as classes que são essencialmente estruturas de estilo C. Não é apenas uma boa prática para entrar.

Depois de definir a interface de classe, você nunca pode ser capaz de mudá-lo (diferente de adicionar a ele), porque as pessoas vão construir sobre ela e contar com ele. Fazendo um meio públicas variáveis ??que você precisa ter essa variável, e você precisa se certificar de que tem o que as necessidades do usuário.

Agora, se você usar um getter, você está prometendo fornecer algumas informações, que atualmente é mantido nessa variável. Se a situação muda, e você prefere não manter essa variável o tempo todo, você pode alterar o acesso. Se a mudança de requisitos (e eu vi algumas mudanças de requisitos muito estranho), e você principalmente precisa do nome dessa nesta variável, mas às vezes o único nessa variável, você pode simplesmente mudar o getter. Se você fez o público variável, você seria preso com ele.

Isto não acontece sempre, mas acho que é muito mais fácil apenas para escrever um getter rápida do que para analisar a situação para ver se eu ia me arrepender de fazer o público variável (e risco de estar errado mais tarde).

Fazendo variáveis ??membro privado é um bom hábito de entrar. Qualquer loja que tem padrões de código é provavelmente vai proibir tornar público o variável de membro ocasional, e qualquer loja com revisões de código é provável que criticá-lo por isso.

Sempre que realmente não importa para facilitar a escrita, adquirir o hábito mais seguro.

idéias coletados de vários C ++ fontes e colocá-lo em uma boa, ainda exemplo bastante simples para getters / setters em 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

Você pode testá-lo on-line aqui: http://codepad.org/zosxqjTX

PS: FO Yvette <3

A partir da teoria Padrões de Projeto; "Encapsular o que varia". Ao definir um 'getter' não há boa adesão ao princípio acima. Assim, se a implementação em representação do sócio mudar no futuro, o membro pode ser 'massageado' antes de retornar do 'getter'; implicando nenhum código de refatoração no lado do cliente onde a chamada 'getter' é feita.

Saudações,

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top