Por que posso acessar variáveis ​​privadas no construtor de cópia?

88

Aprendi que nunca posso acessar uma variável privada, apenas com uma função get na classe. Mas então por que posso acessá-lo no construtor de cópia?

Exemplo:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

Minha declaração:

private:
  T *pFirst,*pLast,*pEnd;
Rei demônio
fonte
Porque o construtor de cópia é um membro da classe por padrão, e alguns outros também.
DumbCoder
+ 53 / -0? Quem votou nisso? De que outra forma você os copiaria ?!? (Vamos desmascarar as não alternativas: Faça um getter de referência público para cada membro privado? Então eles não são privados. Faça um const&getter público ou por valor para cada um? Então eles são apenas 'write-private', & pois os valores desperdiçam recursos e falham para membros não copiáveis.) Estou perplexo com o sucesso de uma pergunta tão vazia, perguntando sobre a construção de cópias, ignorando totalmente o que significa, e nenhuma resposta usa lógica básica para desmascará-la. Eles explicam detalhes técnicos áridos, mas há uma resposta muito mais simples para uma pergunta tão cerrada
sublinhado_d
8
@underscore_d, "De que outra forma você os copiaria?" é uma resposta muito estranha na minha opinião. É como responder "como funciona a gravidade?" com "de que outra forma as coisas cairiam!" Confundir o encapsulamento em nível de classe com o encapsulamento em nível de objeto é de fato bastante comum. É engraçado como você parece pensar que é uma pergunta estúpida e que a resposta deveria ser óbvia. Tenha em mente que o encapsulamento em Smalltalk (discutivelmente a linguagem OO arquetípica) realmente funciona no nível do objeto.
aioobe
@aioobe Bom argumento, obrigado. Meu comentário é meio extremo - talvez a máquina de café tenha quebrado naquele dia. Agradeço por você apontar por que essa pergunta seria popular, especialmente entre aqueles que vêm de outras (e talvez mais) linguagens OO. Na verdade, é discutível que meu comentário foi o que estava "piscando", já que eu estava escrevendo a partir da perspectiva de alguém que programa principalmente em C ++. Além disso, adoro essa analogia da gravidade!
sublinhado_d

Respostas:

33

IMHO, as respostas existentes explicam mal o "Porquê" disso - concentrando-se demais em reiterar qual comportamento é válido. "modificadores de acesso funcionam no nível da classe, e não no nível do objeto." - sim mas por quê?

O conceito abrangente aqui é que são os programadores que projetam, escrevem e mantêm uma classe de quem se espera que entendam o encapsulamento OO desejado e tenham o poder de coordenar sua implementação. Então, se você está escrevendo class X, você está codificando não apenas como um X xobjeto individual pode ser usado pelo código com acesso a ele, mas também como:

  • classes derivadas são capazes de interagir com ele (por meio de funções virtuais opcionalmente puras e / ou acesso protegido), e
  • Xobjetos distintos cooperam para fornecer os comportamentos pretendidos, ao mesmo tempo que respeitam as pós-condições e invariantes de seu projeto.

Não é apenas o construtor de cópia - muitas operações podem envolver duas ou mais instâncias de sua classe: se você está comparando, adicionando / multiplicando / dividindo, construindo cópias, clonando, atribuindo etc., então é comum que você ou simplesmente deve ter acesso a dados privados e / ou protegidos no outro objeto, ou deseja permitir uma implementação de função mais simples, rápida ou geralmente melhor.

Especificamente, essas operações podem querer tirar proveito do acesso privilegiado para fazer coisas como:

  • (construtores de cópia) usam um membro privado do objeto "rhs" (lado direito) em uma lista de inicializadores, de modo que uma variável de membro seja ela própria construída por cópia em vez de construída por padrão (se até mesmo válida) e então atribuída também (novamente, se legal)
  • compartilhe recursos - identificadores de arquivo, segmentos de memória compartilhada, shared_ptrs para referenciar dados etc.
  • apropriar-se das coisas, por exemplo, auto_ptr<>"move" a propriedade para o objeto em construção
  • copie o "cache" privado, calibração ou membros do estado necessários para construir o novo objeto em um estado de uso ideal sem ter que regenerá-los do zero
  • copiar / acessar informações de diagnóstico / rastreamento mantidas no objeto que está sendo copiado que não podem ser acessadas por meio de APIs públicas, mas podem ser usadas por algum objeto de exceção posterior ou registro (por exemplo, algo sobre o tempo / circunstâncias em que a instância "original" não construída por cópia foi construído)
  • execute uma cópia mais eficiente de alguns dados: por exemplo, objetos podem ter, por exemplo, um unordered_mapmembro, mas publicamente apenas expor begin()e end()iteradores - com acesso direto a size()você, pode ter reservecapacidade para uma cópia mais rápida; pior ainda se eles apenas expõem at()e insert()e de outra forma throw....
  • copiar referências de volta para objetos pai / coordenação / gerenciamento que podem ser desconhecidos ou somente gravação para o código do cliente
Tony Delroy
fonte
2
Acho que o maior 'porquê' é que seria uma tremenda sobrecarga de tempo de execução verificar se this == othercada vez que você acessa, o other.xque seria necessário, os modificadores de acesso funcionariam no nível do objeto.
aioobe
2
@aioobe Acho que sua resposta deveria ser muito, muito mais proeminente. Embora a resposta de Tony seja muito boa e conceitual, se eu fosse um homem de apostas, apostaria que sua resposta é o verdadeiro motivo histórico para a escolha. Não só tem mais desempenho, mas também é muito mais simples. Seria uma ótima pergunta para Bjarne!
Nir Friedman
Eu marquei sua resposta, porque ela explica o contexto;)
demonking
@demonking, acho que as razões dadas nesta resposta cobrem porque é conveniente deixar os dados privados serem abertos para outros objetos. Mas os modificadores de acesso não são feitos para tornar os dados "abertamente" o suficiente. Em vez disso, destinam-se a tornar os dados suficientemente fechados para encapsulamento. (Em termos de conveniência , seria ainda melhor se as variáveis ​​privadas fossem públicas!) Atualizei minha resposta com uma seção que acho que aborda melhor o porquê real .
aioobe
@aioobe: comentários antigos, mas enfim ... "verifique se this == othercada vez que você acessa other.x" - erra o ponto - se other.xsó fosse aceito em tempo de execução quando equivalente a this.x, não haveria muito ponteiro escrito other.xem primeiro lugar; o compilador também pode forçá-lo a escrever if (this == other) ...this.x...o que quer que você vá fazer. Sua concepção de "conveniência (ainda mais se as variáveis ​​privadas fossem públicas)" também perde o ponto - a forma como o Padrão é definido é restritiva o suficiente para permitir o encapsulamento adequado , mas não desnecessariamente inconveniente.
Tony Delroy
108

Os modificadores de acesso funcionam no nível da classe , e não no nível do objeto .

Ou seja, dois objetos da mesma classe podem acessar os dados privados um do outro.

Por quê:

Principalmente devido à eficiência. Seria uma sobrecarga de tempo de execução não desprezível verificar se this == othercada vez other.xque você acessasse, seria necessário se os modificadores de acesso funcionassem no nível do objeto.

Também é semanticamente lógico se você pensar nisso em termos de escopo: "Qual grande parte do código eu preciso ter em mente ao modificar uma variável privada?" - Você precisa manter o código de toda a classe em mente, e isso é ortogonal aos objetos existentes em tempo de execução.

E é extremamente conveniente ao escrever construtores de cópia e operadores de atribuição.

aioobe
fonte
35

Você pode acessar membros privados de uma classe de dentro da classe, mesmo aqueles de outra instância.

Alexander Rafferty
fonte
10

Para entender a resposta, gostaria de lembrar alguns conceitos.

  1. Não importa quantos objetos você crie, há apenas uma cópia de uma função na memória para essa classe. Isso significa que as funções são criadas apenas uma vez. No entanto, as variáveis ​​são separadas para cada instância da classe.
  2. this o ponteiro é passado para todas as funções quando chamado.

Agora, por causa do thisponteiro, a função é capaz de localizar variáveis ​​daquela instância particular. não importa se é privado ou público. ele pode ser acessado dentro dessa função. Agora, se passarmos um ponteiro para outro objeto da mesma classe. usando este segundo ponteiro, poderemos acessar membros privados.

espero que isso responda sua pergunta.

Ali Zaib
fonte
6

O construtor de cópia é a função de membro da classe e, como tal, tem acesso aos membros de dados da classe, mesmo aqueles declarados como 'privados'.

Bojan Komazec
fonte
3
Como alguém que conhece a língua, entendo o que você quer dizer. Porém, se eu não soubesse o idioma, pensaria que você estava apenas repetindo a pergunta para mim.
San Jacinto