O C ++ poderia não ter evitado o idioma PIMPL?
-
06-07-2019 - |
Pergunta
Pelo que entendi, o idioma do PIMPL existe apenas porque o C ++ o força a colocar todos os membros da classe particular no cabeçalho. Se o cabeçalho conter apenas a interface pública, teoricamente, qualquer alteração na implementação da classe não exigiria um recompile para o restante do programa.
O que eu quero saber é por que o C ++ não foi projetado para permitir essa conveniência. Por que exige que as partes íntimas de uma classe sejam exibidas abertamente no cabeçalho (sem trocadilhos)?
Solução
Isso tem a ver com o tamanho do objeto. O arquivo H é usado, entre outras coisas, para determinar o tamanho do objeto. Se os membros privados não forem dados nele, você não saberia o tamanho de um objeto para o novo.
Você pode simular, no entanto, o comportamento desejado pelo seguinte:
class MyClass
{
public:
// public stuff
private:
#include "MyClassPrivate.h"
};
Isso não aplica o comportamento, mas retira as coisas privadas do arquivo .h. No lado negativo, isso adiciona outro arquivo para manter. Além disso, no Visual Studio, o IntelliSense não funciona para os membros particulares - isso pode ser uma mais ou menos.
Outras dicas
Eu acho que há uma confusão aqui. O problema não é sobre cabeçalhos. Os cabeçalhos não fazem nada (são apenas maneiras de incluir bits comuns de texto de origem entre vários arquivos de código de origem).
O problema, por mais que exista, é que as declarações de classe no C ++ precisam definir tudo, público e privado, que uma instância precisa ter para trabalhar. (O mesmo se aplica ao Java, mas a maneira como a referência às classes compiladas externamente trabalha torna o uso de qualquer coisa como cabeçalhos compartilhados desnecessários.)
É da natureza das tecnologias comuns orientadas a objetos (não apenas do C ++) que alguém precisa conhecer a classe concreta usada e como usar seu construtor para fornecer uma implementação, mesmo se você estiver usando apenas as partes públicas. O dispositivo em (3, abaixo) o esconde. A prática em (1, abaixo) separa as preocupações, quer você (3) ou não.
Use classes abstratas que definem apenas as partes públicas, principalmente métodos, e deixe a classe de implementação herdar dessa classe abstrata. Portanto, usando a convenção usual para cabeçalhos, há um abstrato.HPP que é compartilhado. Há também uma implementação.HPP que declara a classe herdada e que só é transmitida para os módulos que implementam métodos da implementação. O arquivo implementation.hpp #include "abstract.hpp" para uso na declaração de classe que faz, para que haja um único ponto de manutenção para a declaração da interface abstraída.
Agora, se você deseja aplicar o esconderijo da declaração da classe de implementação, precisa ter alguma maneira de solicitar a construção de uma instância concreta sem possuir a declaração de classe completa e específica: você não pode usar novo e não pode usar instâncias locais . (Você pode excluir.
Juntamente com ou como parte do arquivo de cabeçalho que é usado como definição compartilhada para a classe/interface abstrata, inclua assinaturas de função para funções de auxiliar externo. Essas funções devem ser implementadas em módulos que fazem parte das implementações de classe específicas (para que vejam a declaração completa da classe e possam exercer o construtor). A assinatura da função auxiliar é provavelmente muito parecida com a do construtor, mas retorna uma referência de instância como resultado (esse proxy do construtor pode retornar um ponteiro nulo e pode até lançar exceções se você gosta desse tipo de coisa). A função ajudante constrói uma instância de implementação específica e retorna que ele foi referido a uma instância da classe abstrata.
Missão cumprida.
Ah, e a recompilação e o rolagem devem funcionar da maneira que você deseja, evitando a recompilação dos módulos de chamada quando apenas a implementação muda (pois o módulo de chamada não faz mais alocações de armazenamento para as implementações).
Vocês estão todos ignorando o ponto da pergunta -
Por que o desenvolvedor deve digitar o código PIMPL?
Para mim, a melhor resposta que posso encontrar é que não temos uma boa maneira de expressar código C ++ que permite operar nele. Por exemplo, o tempo de compilação (ou pré-processador, ou qualquer outra coisa) ou um código DOM.
O C ++ precisa muito de um ou de ambos disponíveis para um desenvolvedor para fazer metaprogramação.
Então você pode escrever algo assim em seu myclass público.h:
#pragma pimpl(MyClass_private.hpp)
E então escreva o seu próprio gerador de invólucro muito trivial.
Alguém terá uma resposta muito mais detalhada do que eu, mas a resposta rápida é dupla: o compilador precisa conhecer todos os membros de uma estrutura para determinar os requisitos de espaço de armazenamento, e o compilador precisa conhecer a ordem desses membros para gerar compensações de maneira determinística.
O idioma já é bastante complicado; Eu acho que um mecanismo para dividir as definições de dados estruturados no código seria um pouco de calamidade.
Normalmente, eu sempre vi classes de políticas usado para definir o comportamento de implementação em um manicolor do PIMPL. Eu acho que existem alguns benefícios adicionais do uso de um padrão de política - mais fácil de trocar implementações, pode combinar facilmente várias implementações parciais em uma única unidade que permite dividir o código de implementação em unidades funcionais e reutilizáveis, etc.
Pode ser porque o tamanho da classe é necessário ao passar em sua instância por valores, agregá -la em outras classes, etc?
Se o C ++ não suportasse a semântica do valor, teria sido bom, mas acontece.
Sim mas...
Você precisa ler o livro "Design e evolução de C ++" da Stroustrup. Teria inibido a captação de C ++.