Por que colocamos funções de membro privadas em cabeçalhos?

16

A resposta para por que colocamos variáveis ​​de membro privadas nos cabeçalhos de C ++ é que o tamanho da classe deve ser conhecido nos pontos em que as instâncias são declaradas, para que o compilador possa gerar código que se mova adequadamente sobre a pilha.

Por que precisamos colocar membros privados em cabeçalhos?

Mas existe algum motivo para declarar funções privadas na definição de classe?

A alternativa seria essencialmente o idioma pimpl, mas sem a indireção supérflua.

Esse recurso de idioma é mais do que um erro histórico?

Praxeolitic
fonte

Respostas:

11

As funções membro privadas podem ser virtuale, nas implementações comuns do C ++ (que usam uma tabela v), é necessário que a ordem e o número específicos de funções virtuais sejam conhecidos por todos os clientes da classe. Isso se aplica mesmo que uma ou mais funções-membro virtuais sejam private.

Pode parecer que isso é como "colocar o carro antes do cavalo", porque as opções de implementação do compilador não devem afetar a especificação da linguagem. No entanto, na realidade, a própria linguagem C ++ foi desenvolvida ao mesmo tempo que uma implementação de trabalho ( Cfront ), que usava vtables.

Greg Hewgill
fonte
4
"opções de implementação não devem afetar a linguagem" é quase exatamente o oposto do mantra C ++. A especificação não exige nenhuma implementação específica, mas existem muitas regras criadas especificamente para atender aos requisitos de uma opção de implementação particularmente eficiente.
Ben Voigt 16/05
Isso parece muito plausível. Existe uma fonte sobre isso?
Praxeolitic
@Praxeolitic: Você pode ler sobre a tabela de métodos Virtual .
Greg Hewgill
1
@Praxeolitic - encontre uma cópia antiga do 'The Annotated C ++ Refernce Manual' ( stroustrup.com/arm.html ) - os autores falam sobre vários detalhes da implementação e como ela moldou a linguagem.
Michael Kohne 16/05
Não tenho certeza se isso realmente responde à pergunta. Para mim, trata apenas de uma faceta específica - embora bem - e deixa para trás o resto: por que precisamos colocar funções de membro privadas não virtuais nos cabeçalhos? Certamente, puramente por consistência / simplicidade é um argumento - mas, idealmente, também teríamos uma lógica mecanicista e / ou filosófica. Então, adicionei uma resposta que acredito que explica isso como uma escolha de design muito deliberada, com efeitos muito benéficos.
Underscore_d
13

Se você permitisse que métodos fossem adicionados a uma classe fora de sua definição, eles poderiam ser adicionados em qualquer lugar , em qualquer arquivo, por qualquer pessoa.

Isso daria imediatamente a todo código do cliente acesso trivial a membros de dados privados e protegidos.

Depois de terminar a definição da classe, não há como marcar alguns arquivos como sendo especialmente abençoados pelo autor para estendê-lo - existem apenas unidades de tradução simples. Portanto, a única maneira razoável de dizer ao compilador que um conjunto específico de métodos é oficial ou abençoado pelo autor da classe é declará-los dentro da classe.


Observe que temos acesso direto à memória em C ++, o que significa que geralmente é trivial criar um tipo de sombra com o mesmo layout de memória da sua classe, adicionar meus próprios métodos (ou apenas tornar todos os dados públicos) e reinterpret_cast. Ou posso encontrar o código da sua função privada ou desmontá-lo. Ou procure o endereço da função na tabela de símbolos e ligue ou diretamente.

Esses especificadores de acesso não tentam impedir esses ataques, porque isso não é possível. Eles indicam apenas como uma classe deve ser usada.

Sem utilidade
fonte
1
A definição de classe pode se referir a uma entidade de contêiner de função oculta do código do cliente. Como alternativa, isso pode ser invertido e uma definição de classe tradicional completa oculta do código do cliente pode designar uma interface visível que apenas descreve membros públicos e pode revelar seu tamanho.
Praxeolitic
1
Claro, mas o modelo de compilação não fornece uma maneira razoável de impedir que o código do cliente interponha sua própria versão do contêiner oculto ou uma interface visível diferente com acessadores extras.
Inútil
Esse é um bom argumento, mas não é verdade para as definições de todas as funções-membro que não estão no cabeçalho?
Praxeolitic
1
Todas as funções membro são declaradas dentro da classe. O ponto é que você não pode adicionar novas funções de membro que não foram pelo menos declaradas na definição de classe (e a regra de uma definição impede várias definições).
Inútil
Então, o que dizer de um único contêiner de regra de funções privadas?
Praxeolitic
8

A resposta aceita explica isso para funções privadas virtuais , mas isso apenas responde a uma faceta específica da pergunta, que é consideravelmente mais limitada do que o que o OP pediu. Portanto, precisamos reformular: Por que somos obrigados a declarar funções privadas não virtuais nos cabeçalhos?

Outra resposta invoca o fato de que as classes devem ser declaradas em um bloco - após o qual são seladas e não podem ser adicionadas. Isso é o que você faria ao omitir a declaração de um método privado no cabeçalho e tentar defini-lo em outro lugar. Bom ponto. Por que alguns usuários da classe podem aumentá-lo de uma maneira que outros usuários não podem observar? Métodos particulares fazem parte e não são excluídos disso. Mas então você pergunta por que eles estão incluídos, e parece um pouco tautológico. Por que os usuários da classe precisam saber sobre eles? Se eles não estavam visíveis, os usuários não poderiam adicionar nenhum, e ei presto.

Então, eu queria fornecer uma resposta que, em vez de incluir métodos privados por padrão, forneça pontos específicos em favor de torná-los visíveis para os usuários. Um motivo mecanicista para funções privadas não virtuais que requerem declaração pública é apresentado no GotW # 100 de Herb Sutter sobre o idioma Pimpl como parte de sua lógica. Não vou falar sobre o Pimpl aqui, pois tenho certeza de que todos sabemos disso. Mas aqui está a parte relevante:

No C ++, quando qualquer coisa em uma definição de classe de arquivo de cabeçalho é alterada, todos os usuários dessa classe devem ser recompilados - mesmo que a única alteração tenha sido nos membros da classe privada que os usuários da classe não podem acessar. Isso ocorre porque o modelo de compilação do C ++ é baseado na inclusão de texto e porque o C ++ supõe que os chamadores saibam duas coisas principais sobre uma classe que pode ser afetada por membros particulares:

  • Tamanho e layout : [de membros e funções virtuais - auto-explicativas e ótimas para desempenho, mas não por que estamos aqui]
  • Funções : O código de chamada deve poder resolver chamadas para funções-membro da classe, incluindo funções privadas inacessíveis que sobrecarregam com funções não privadas - se a função privada for uma correspondência melhor, o código de chamada falhará na compilação. (O C ++ tomou a decisão deliberada de design de executar a resolução de sobrecarga antes da verificação da acessibilidade por motivos de segurança. Por exemplo, considerou-se que alterar a acessibilidade de uma função de privada para pública não deveria alterar o significado do código de chamada legal.)

Sutter é, é claro, uma fonte extremamente confiável como membro do Comitê, então ele conhece "uma decisão deliberada de projeto" quando vê uma. E a idéia de exigir declaração pública de métodos privados como uma maneira de evitar a semântica alterada ou a acessibilidade acidentalmente interrompida posteriormente é provavelmente a lógica mais convincente. Felizmente, como a coisa toda parecia bastante inútil antes de agora!

underscore_d
fonte
" Mas então você pergunta por que, e essa resposta parece tautológica. Novamente, por que os usuários da classe precisam saber sobre eles? " Não, não é tautológico. Os usuários não "necessariamente precisam saber sobre eles". O que precisa acontecer é que você deve ser capaz de impedir que as pessoas estendam as aulas sem o consentimento do redator da classe. Portanto, todos esses membros devem ser declarados antecipadamente. Isso faz com que os usuários saibam sobre membros privados é apenas uma consequência necessária.
Nicol Bolas
1
@NicolBolas Com certeza. Isso provavelmente não foi uma redação muito boa da minha parte. Eu quis dizer que a resposta apenas explica a visibilidade de métodos privados como conseqüência de uma regra (muito válida) que cobre muitas coisas, em vez de fornecer uma justificativa sobre a visibilidade de métodos privados especificamente. Obviamente, a resposta real é uma combinação de Useless, gnasher e minha. Eu só quero fornecer outra perspectiva que ainda não estava aqui.
Underscore_d
4

Há duas razões para fazer isso.

Primeiro, perceba que o especificador de acesso é para o compilador e não é relevante no tempo de execução. Acessar um membro privado fora do escopo é um erro de compilação .

Concisão

Considere uma função curta, uma ou duas linhas. Existe para reduzir a replicação de código em outro lugar, o que também tem a vantagem de poder alterar a maneira como um algoritmo ou qualquer outra coisa funciona em um local, em vez de muitos (por exemplo, alterar um algoritmo de classificação).

Você prefere ter uma ou duas linhas rápidas no cabeçalho ou ter o protótipo de função lá mais uma implementação em algum lugar? É mais fácil encontrar no cabeçalho e, para funções curtas, é muito mais detalhado ter uma implementação separada.

Há outra grande vantagem, que é ...

Funções em linha

Uma função privada pode ser incorporada e isso necessariamente exige que ela esteja no cabeçalho. Considere isto:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

A função privada pode ser incorporada juntamente com a função pública. Isso é feito a critério do compilador, pois a inlinepalavra-chave é tecnicamente uma sugestão , não um requisito.

Comunidade
fonte
Todas as funções definidas dentro do corpo da classe são marcadas automaticamente inline, não há motivo para usar a palavra-chave lá.
Ben Voigt
@BenVoigt assim é. Eu não percebi isso e tenho usado C ++ em tempo parcial por um bom tempo. Essa linguagem nunca deixa de me surpreender com pequenas pepitas como essa.
Eu não acho que um método precise estar no cabeçalho para ser embutido. Ele só precisa estar na mesma unidade de compilação. Existe um caso em que faz sentido ter unidades de compilação separadas para uma classe?
Samuel Danielson
@SamuelDanielson correto, mas uma função privada deve estar na definição de classe. A própria noção de "privado" implica que faz parte da classe. Seria possível ter uma função de não- .cppmembro no arquivo, destacada por funções-membro definidas fora da definição de classe, mas essa função não seria privada.
O corpo da questão era "existe algum motivo para declarar funções privadas na definição de classe [sic, pela qual o contexto mostra OP realmente significa declaração de classe]". Você está falando sobre definir as funções privadas. Isso não responde à pergunta. @SamuelDanielson Atualmente, o LTO significa que as funções podem estar em qualquer lugar do projeto e manter a mesma chance de serem incorporadas. Quanto à divisão de uma classe em várias unidades de tradução, o caso mais simples é que a classe é apenas grande e você deseja dividi-la semanticamente em vários arquivos de origem. Tenho certeza de que tais classes grandes são desencorajados, mas de qualquer maneira
underscore_d
2

Outro motivo para ter métodos particulares no arquivo de cabeçalho: Há casos em que um método público interno não passa de uma chamada de um ou vários métodos particulares. Ter os métodos privados no cabeçalho significa que uma chamada para o método público pode ser completamente incorporada ao código real dos métodos privados, e o inlining não para com uma chamada para o método privado. Mesmo de uma unidade de compilação diferente (e métodos públicos geralmente seriam chamados de unidades de compilação diferentes).

É claro que também existe o motivo pelo qual o compilador não pode detectar problemas com a resolução de sobrecarga se não conhecer todos os métodos, inclusive os privados.

gnasher729
fonte
Excelente ponto sobre inlining! Eu imagino que isso seja especialmente relevante para o stdlib e outras bibliotecas com métodos definidos em linha. (se não tanto para código interno, onde LTO pode fazer quase qualquer coisa para além das fronteiras TU)
underscore_d
0

É para permitir que essas funções acessem os membros privados. Caso contrário, você precisará frienddeles no cabeçalho de qualquer maneira.

Se alguma função pudesse acessar os membros privados da classe, o privado seria inútil.

catraca arrepiante
fonte
1
Talvez eu devesse ter sido mais explícito na pergunta - eu quis dizer por que a linguagem é projetada dessa maneira? Por que deve ser ou por que esse design é bom?
Praxeolitic 15/05