getters C ++ / estilo formadores de codificação
-
09-09-2019 - |
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.
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,