Por que as variáveis ​​privadas são descritas no arquivo de cabeçalho acessível ao público?

11

OK, espero que seja uma pergunta subjetiva o suficiente para programadores, mas aqui vai. Estou continuamente ampliando meu conhecimento de linguagens e práticas de engenharia de software ... e encontrei algo que simplesmente não faz sentido para mim.

No C ++, as declarações de classe incluem private:métodos e parâmetros no arquivo de cabeçalho, que, teoricamente, é o que você passa ao usuário para incluir se você criar uma lib.

No Objective-C, @interfaces fazem praticamente a mesma coisa, forçando você a listar seus membros privados (pelo menos, há uma maneira de obter métodos particulares no arquivo de implementação).

Pelo que sei, Java e C # permitem fornecer uma interface / protocolo que pode declarar todas as propriedades / métodos acessíveis ao público e oferece ao codificador a capacidade de ocultar todos os detalhes de implementação no arquivo de implementação.

Por quê? O encapsulamento é um dos princípios principais do POO, por que o C ++ e o Obj-C não possuem essa capacidade básica? Existe algum tipo de prática recomendada para o Obj-C ou C ++ que oculta toda a implementação?

Obrigado,

Stephen Furlani
fonte
4
Eu sinto sua dor. Fugi gritando do C ++ na primeira vez em que adicionei um campo privado a uma classe e tive que recompilar tudo o que o usava.
Larry Coleman
@ Larry, eu não me importo muito com isso, mas parece ser anunciado como uma ótima linguagem OO, mas não pode nem mesmo encapsular "corretamente".
Stephen Furlani
2
Não acredite no hype. Existem maneiras melhores de fazer OO, nos campos de digitação estático e dinâmico.
Larry Coleman
É uma grande linguagem em alguns aspectos, mas não por causa de suas habilidades OO, que são um enxerto de Simula 67 em C.
David Thornley
Você deve fazer alguma programação em C. Então você terá uma melhor compreensão de por que essas linguagens são do jeito que são, incluindo Java C # etc.
Henry

Respostas:

6

A questão é se o compilador precisa saber o tamanho de um objeto. Nesse caso, o compilador precisa conhecer os membros privados para contá-los.

Em Java, existem tipos e objetos primitivos, e todos os objetos são alocados separadamente, e as variáveis ​​que os contêm são realmente ponteiros. Portanto, como um ponteiro é um objeto de tamanho fixo, o compilador sabe o tamanho da coisa que uma variável representa, sem saber o tamanho real do objeto apontado. O construtor lida com tudo isso.

No C ++, é possível ter objetos representados localmente ou no heap. Portanto, o compilador precisa saber qual o tamanho de um objeto, para que ele possa alocar uma variável ou matriz local.

Às vezes, é desejável dividir a funcionalidade da classe em uma interface pública e em todo o resto privado, e é aí que a técnica PIMPL (Ponteiro para IMPLementação) entra. A classe terá um ponteiro para uma classe de implementação privada, e os métodos da classe pública podem chamar naquela.

David Thornley
fonte
o compilador não pode procurar a biblioteca de objetos para obter essas informações? Por que essas informações não podem ser legíveis por máquina em vez de legíveis por humanos?
Stephen Furlani
@ Stephen: No momento da compilação, não há necessariamente uma biblioteca de objetos. Como o tamanho dos objetos afeta o código compilado, ele deve ser conhecido no momento da compilação, não apenas no tempo do link. Além disso, é possível para Ah definir a classe A, Bh para definir a classe B e as definições de função em A.cpp para usar objetos da classe B e vice-versa. Nesse caso, seria necessário compilar cada um de A.cpp e B.cpp antes do outro, se estiver usando o tamanho do objeto no código do objeto.
David Thornley
o link não pode ser feito durante a compilação? Confesso que não sei detalhes nessa profundidade, mas se a interface de um objeto expõe seu conteúdo, esse mesmo conteúdo não pode ser exposto de uma biblioteca ou outro componente legível por máquina? eles devem ser definidos em um arquivo de cabeçalho acessível ao público? Entendo que isso faz parte da maioria das línguas C, mas precisa ser assim?
Stephen Furlani
1
@ Stephen: O link só pode ser feito durante a compilação se todos os componentes apropriados estiverem lá. Mesmo assim, seria um processo complicado, envolvendo compilação parcial (tudo menos o tamanho do objeto), vinculação parcial (para obter o tamanho do objeto) e compilação e vinculação final. Dado que C e C ++ são linguagens relativamente antigas, isso simplesmente não iria acontecer. Presumivelmente, alguém poderia criar um novo idioma sem os arquivos de cabeçalho, mas não seria C ++.
David Thornley
14

Devido ao design do C ++, para criar um objeto na pilha, o compilador deve saber o tamanho dele. Para fazer isso, ele precisa ter todos os campos presentes no arquivo de cabeçalho, pois é tudo o que o compilador pode ver quando o cabeçalho é incluído.

Por exemplo, se você definir uma classe

class Foo {
    public int a;
    private int b;
};

então sizeof(Foo)é sizeof(a) + sizeof(b). Se houvesse algum mecanismo para separar os campos particulares, o cabeçalho poderia conter

class Foo {
    public int a;
};

com sizeof(Foo) = sizeof(a) + ???.

Se você realmente deseja ocultar dados privados, tente o idioma pimpl, com

class FooImpl;
class Foo {
    private FooImpl* impl;
}

no cabeçalho e uma definição FooImplapenas para Fooo arquivo de implementação.

Scott Wales
fonte
Oh Eu não tinha ideia de que era esse o caso. É por isso que linguagens como Java, C # e Obj-C têm "Objetos de Classe" para que possam perguntar à classe qual o tamanho do objeto?
Stephen Furlani
4
Tente usar um pimpl, um ponteiro para uma implementação privada. Como todos os ponteiros de classe têm o mesmo tamanho, você não precisa definir a classe de implementação, apenas a declare.
Scott Wales
Eu acho que deveria ser uma resposta em vez de um comentário.
Larry Coleman
1

Tudo isso se resume à escolha do design.

Se você realmente deseja ocultar os detalhes de implementação privada da classe C ++ ou Objective-C, fornece uma ou mais interfaces suportadas pela classe (classe virtual pura em C ++, Objective-C @protocol) e / ou cria a classe capaz de se construir, fornecendo um método de fábrica estático ou um objeto de fábrica de classe.

A razão pela qual as variáveis particulares são expostas no cabeçalho do arquivo / declaração de classe / @ interface é que um consumidor de sua classe talvez seja necessário criar uma nova instância do mesmo e um new MyClass()ou [[MyClass alloc]init]no código do cliente precisa o compilador para entender o quão grande MyClass um objeto está em ordem para fazer a alocação.

Java e C # também têm suas variáveis ​​privadas detalhadas na classe - elas não são exceção a isso, mas na IMO o paradigma da interface é muito mais comum com essas linguagens. Você pode não ter o código fonte em cada caso, mas há metadados suficientes no código compilado / byte para deduzir essas informações. Como C ++ e Objective-C não possuem esses metadados, a única opção são os detalhes reais da interface class / @. No mundo C ++ COM, você não expõe as variáveis ​​privadas de nenhuma classe, mas pode fornecer o arquivo de cabeçalho - porque a classe é totalmente virtual. Um objeto de fábrica de classe também é registrado para criar as instâncias reais e existem alguns metadados de várias formas.

Em C ++ e Objective-C, é menos trabalhoso distribuir o arquivo de cabeçalho comparado à gravação e manutenção de um arquivo de interface / protocolo adicional. Esse é um dos motivos pelos quais você vê a implementação privada exposta com tanta frequência.

Outro motivo no C ++ são os modelos - o compilador precisa conhecer os detalhes da classe para gerar uma versão dessa classe especializada nos parâmetros fornecidos. O tamanho dos membros da classe varia com a parametrização, portanto, é necessário ter essas informações.

JBRWilkinson
fonte