Pergunta

Eu tenho uma classe recipiente simples com um construtor de cópia.

Você recomendo usar getters e setters, ou acessar as variáveis ??membro diretamente?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • No exemplo, todo o código está em linha, mas em nosso código real, não existe código embutido.

Update (29 de setembro 09):

Algumas dessas respostas são bem escritos no entanto eles parecem ficar faltando o ponto da questão:

  • isso é simples exemplo inventado para discutir usando getters / setters vs variáveis ??

  • initializer listas ou funções de validação privadas não são realmente parte desta questão. Eu estou querendo saber se qualquer projeto vai tornar o código mais fácil de manter e expandir.

  • Alguns ppl estão se concentrando na corda neste exemplo no entanto, é apenas um exemplo, imagine que é um objeto diferente em seu lugar.

  • Eu não estou preocupado com o desempenho. não estamos programando sobre o PDP-11

Foi útil?

Solução

Você antecipar como a string é retornado, por exemplo. espaço em branco aparado, nulo verificado, etc.? Mesmo com SetMyString (), se a resposta for sim, você é melhor fora com métodos de acesso desde que você não tem que alterar seu código em lugares zilhão mas apenas modificar os métodos get e set.

Outras dicas

EDIT: Respondendo a pergunta editado:)

isso é simples exemplo inventado para discutir usando getters / setters vs variáveis ??

Se você tem uma simples coleção de variáveis, que não precisa de qualquer tipo de validação, nem processamento adicional, então você pode considerar o uso de um POD vez. De de Stroustrup FAQ :

classe bem concebido apresenta uma um limpas e interface simples para seus usuários, escondendo sua representação e economia seus usuários de ter que saber sobre que a representação. Se o representação não deve ser escondido - dizer, porque os usuários devem ser capazes de alterar qualquer membro de dados de qualquer maneira que como - você pode pensar que classe como "Apenas uma estrutura de dados planície velho"

Em suma, este não é JAVA. você não deve escrever getters simples / setters porque eles são tão maus como expondo as variáveis ??eles mesmos.

initializer listas ou funções de validação privadas não são realmente parte desta questão. Eu estou querendo saber se qualquer projeto fará o código mais fácil de manter e expandir.

Se você está copiando variáveis ??de outro objeto, então o objeto de origem deve estar em um estado válido. Como o objeto de origem mal formada foi construído em primeiro lugar ?! não deve construtores fazer o trabalho de validação? não são as funções membro modificadores responsável da manutenção da classe invariante pela validação da entrada? Por que você validar um objeto "válido" em um construtor de cópia?

Não estou preocupado com o desempenho. não estamos programando sobre o PDP-11

Este é sobre o estilo mais elegante, embora em C ++ o código mais elegante tem as melhores características de desempenho normalmente.


Você deve usar um initializer list. Em seu código, m_str1 é padrão construído seguida, atribuído um novo valor. Seu código poderia ser algo como isto:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak Você não deve cont.m_str1 IMO validar no copy constructor. O que eu faço, é para validar as coisas em constructors. Validação no meio copy constructor você estiver copiando um objeto mal formado, em primeiro lugar, por exemplo:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

Você deve usar uma lista de inicializador e, em seguida, a questão torna-se sem sentido, como em:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Há uma grande seção de Matthew Wilson Imperfect C ++ que explica tudo sobre listas de membros Initializer, e sobre como você pode usá-los em combinação com const e / ou referências para tornar seu código mais seguro.

Editar : um exemplo mostrando validação e const:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

Como está escrito agora (sem qualificação da entrada ou saída) o seu getter e setter (acessor e modificador, se você preferir) estão realizando absolutamente nada, então você pode muito bem fazer o público corda e ser feito com -lo.

Se o código verdadeiro realmente qualificar a corda, em seguida, as chances são boas bastante de que o que você está lidando com não é propriamente uma corda em tudo - em vez disso, é apenas algo que se parece muito com uma corda. O que você está fazendo realmente, neste caso, é abusar do sistema de tipo, espécie de expor uma corda, quando o tipo real é apenas algo um pouco como uma string. Você está em seguida, fornecendo o setter para tentar impor o que quer que as restrições do tipo real tem comparação com uma corda real.

Quando você olha para ele daquela direção, a resposta torna-se bastante óbvio: em vez de uma string, com um setter para fazer o ato string como algum outro tipo (mais restrito), o que você deve fazer em vez disso é a definição de um real classe para o tipo que você realmente quer. Tendo definido essa classe corretamente, você faz uma instância de la pública. Se (como parece ser o caso aqui) que é razoável atribuir-lhe um valor que começa como uma corda, em seguida, que a classe deve conter um operador de atribuição que recebe uma string como um argumento. Se (como também parece ser o caso aqui) que é razoável para converter esse tipo para uma cadeia em algumas circunstâncias, pode também incluir operador de conversão que produz uma string como resultado.

Isto dá uma melhoria real sobre o uso de um setter e getter em uma classe circundante. Em primeiro lugar, quando você colocar aqueles em uma classe envolvente, é fácil para o código dentro dessa classe para ignorar o getter / setter, perdendo execução de qualquer que seja o setter era para valer. Em segundo lugar, ele mantém uma notação de aparência normal. Usando um getter e um setter obriga você a escrever código que é simplesmente feio e difícil de ler.

Um dos principais pontos fortes de uma classe string em C ++ está usando sobrecarga de operador para que você possa substituir algo como:

strcpy(strcat(filename, ".ext"));

com:

filename += ".ext";

para melhorar a legibilidade. Mas veja o que acontece se essa string é parte de uma classe que nos obriga a passar por um getter e setter:

some_object.setfilename(some_object.getfilename()+".ext");

Se qualquer coisa, o código C é realmente mais legível do que essa bagunça. Por outro lado, considere o que acontece se não fizermos o trabalho direito, com um objeto pública de uma classe que define uma cadeia de operador e operador =:

some_object.filename += ".ext";

Agradável, simples e legível, apenas como deveria ser. Melhor ainda, se temos de impor algo sobre a corda, eu posso inspecionar só isso pequena classe, nós realmente só tem que olhar um ou dois específico, lugares bem conhecidos (operador =, possivelmente um ctor ou dois para essa classe) para sabe que é sempre aplicadas -. uma história totalmente diferente de quando estamos usando um setter para tentar fazer o trabalho

Pergunte-se que os custos e os benefícios são.

Custo: maior sobrecarga de tempo de execução. Chamando funções virtuais em ctors é uma má idéia, mas setters e getters são susceptíveis de ser virtual.

Benefícios: se o setter / getter faz algo complicado, você não está repetindo código; se ele faz algo intuitivo, você não está esquecendo de fazer isso.

A relação custo / benefício será diferente para diferentes classes. Quando estiver apurado que a relação, usar o seu julgamento. Para as classes imutáveis, é claro, você não tem setters, e você não precisa getters (como membros const e referências podem ser públicas como ninguém pode mudar / recolocar-los).

Não há nenhuma bala de prata como a forma de escrever o construtor de cópia. Se sua classe só tem membros que fornecem um construtor de cópia que cria casos que não fazer estado share (ou pelo menos não parecem fazê-lo) usando uma lista de inicializador é um bom caminho.

Caso contrário, você tem que realmente pensa.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

Note que você pode obter em torno da segfault potencial usando smart_ptr -. Mas você pode ter um monte de diversão depurar os erros resultantes

É claro que ele pode ficar ainda mais engraçado.

  • Os membros que são criados sob demanda.
  • new beta(a.beta) é errado no caso de você de alguma forma apresentar polimorfismo.

... um parafuso a caso contrário -. Por favor, sempre acho que quando se escreve um construtor de cópia

Por que você precisa getters e setters em tudo?

Simples :) - Eles preservam invariantes -. Ou seja, garante sua classe marcas, tais como "MyString tem sempre um número par de caracteres"

Se implementado como previsto, o objeto está sempre em um estado válido - por isso uma cópia memberwise pode muito bem copiar os membros diretamente, sem medo de quebrar qualquer garantia. Não há nenhuma vantagem de passar estado já validada através de uma nova rodada de validação do estado.

Como Arak disse, o melhor seria usar uma lista inicializador.


Não é tão simples (1):
Outro motivo para usar getters / setters não está contando com detalhes de implementação. Isso é uma idéia estranha para uma cópia ctor, quando mudar esses detalhes de implementação que você quase sempre precisa ajustar CDA de qualquer maneira.


Não é tão simples (2):
Para provar que estou errado, você pode construir invariantes que são dependentes do próprio exemplo, ou outro fator externo. Um (muito contrieved) exemplo: "se o número de casos é ainda, o comprimento da corda é mesmo, caso contrário, é estranho." Nesse caso, a cópia ctor teria que jogar, ou ajustar a cadeia. Em tal caso, poderia ajudar a usar setters / getters - mas isso não é o cas gerais. Você não deve derivar regras gerais de esquisitices.

Eu prefiro usar uma interface para classes externas para acessar os dados, no caso de você querer mudar a maneira como ele está recuperado. No entanto, quando você está dentro do escopo da classe e quer replicar o estado interno do valor copiado, eu iria com membros de dados diretamente.

Para não mencionar que você provavelmente vai salvar algumas chamadas de função se o getter não são inlined.

Se seus getters são (em linha e) não virtual, não há vantagens nem desvantagens em usá-los wrt acesso membro direto -. Ela só olha pateta para mim em termos de estilo, mas, não é grande coisa de qualquer maneira

Se seus getters são virtuais, em seguida, há é em cima ... mas, no entanto, que é exatamente quando você quiser chamá-los, apenas no caso de eles substituído em uma subclasse -!)

Existe um teste simples que funciona para muitas questões de design, este incluído:. Add efeitos colaterais e ver o que breaks

setter Suponha que não só atribui um valor, mas também escreve registro de auditoria, registra uma mensagem ou gera um evento. Você quer que isso aconteça para cada propriedade ao copiar objeto? Provavelmente não -. Modo chamando setters no construtor é logicamente errado (mesmo que setters são de fato apenas atribuições)

Apesar de concordar com outros cartazes que há muitos de nível de entrada C ++ "não-não é" em sua amostra, colocando isso para o lado e respondendo à sua pergunta diretamente:

Na prática, eu tendem a fazer muitos, mas não todos os meus campos do membro * público para começar, e, em seguida, movê-los para get / set, quando necessário.

Agora, eu serei o primeiro a dizer que isso não é necessariamente uma prática recomendada, e muitos praticantes aborrecerá isso e dizer que todos os campos devem ter setters / getters.

Talvez. Mas acho que, na prática isso nem sempre é necessário. Concedido, que provoca dor mais tarde, quando eu mudar um campo de público para um getter, e às vezes quando eu sei o uso de uma classe terá, eu dar-lhe set / get e tornar o campo protegido ou privado desde o início.

YMMV

RF

  • você chama campos "variáveis" - eu encorajo você a usar esse termo apenas para as variáveis ??locais dentro de uma função / método
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top