Estou procurando a definição de quando tenho permissão para fazer a declaração de encaminhamento de uma classe no arquivo de cabeçalho de outra classe:
Posso fazê-lo para uma classe base, para uma classe realizada como membro, para uma classe passada para a função de membro por referência, etc.?
c++
forward-declaration
c++-faq
Igor Oks
fonte
fonte
Respostas:
Coloque-se na posição do compilador: quando você encaminha um tipo, tudo o que o compilador sabe é que esse tipo existe; não sabe nada sobre seu tamanho, membros ou métodos. É por isso que é chamado de tipo incompleto . Portanto, você não pode usar o tipo para declarar um membro ou uma classe base, pois o compilador precisaria conhecer o layout do tipo.
Assumindo a seguinte declaração antecipada.
Aqui está o que você pode e não pode fazer.
O que você pode fazer com um tipo incompleto:
Declare um membro como um ponteiro ou uma referência ao tipo incompleto:
Declarar funções ou métodos que aceitam / retornam tipos incompletos:
Defina funções ou métodos que aceitam / retornam indicadores / referências ao tipo incompleto (mas sem usar seus membros):
O que você não pode fazer com um tipo incompleto:
Use-o como uma classe base
Use-o para declarar um membro:
Definir funções ou métodos usando este tipo
Use seus métodos ou campos, na verdade tentando desreferenciar uma variável com tipo incompleto
Quando se trata de modelos, não há regra absoluta: se você pode usar um tipo incompleto como parâmetro do modelo depende da maneira como o tipo é usado no modelo.
Por exemplo,
std::vector<T>
requer que seu parâmetro seja um tipo completo, enquantoboost::container::vector<T>
não. Às vezes, um tipo completo é necessário apenas se você usar determinadas funções de membro; esse é o casostd::unique_ptr<T>
, por exemplo.Um modelo bem documentado deve indicar em sua documentação todos os requisitos de seus parâmetros, incluindo se eles precisam ser do tipo completo ou não.
fonte
A regra principal é que você só pode declarar encaminhamento de classes cujo layout de memória (e, portanto, funções de membros e membros de dados) não precisa ser conhecido no arquivo que você declara encaminhar.
Isso excluiria as classes base e qualquer coisa, exceto as classes usadas por referências e ponteiros.
fonte
Lakos distingue entre uso de classe
Eu nunca vi isso pronunciado de forma mais sucinta :)
fonte
Além de ponteiros e referências a tipos incompletos, você também pode declarar protótipos de funções que especificam parâmetros e / ou retornam valores que são tipos incompletos. No entanto, você não pode definir uma função com um parâmetro ou tipo de retorno incompleto, a menos que seja um ponteiro ou referência.
Exemplos:
fonte
Até agora, nenhuma das respostas descreve quando é possível usar uma declaração direta de um modelo de classe. Então, aqui vai.
Um modelo de classe pode ser encaminhado declarado como:
Seguindo a estrutura da resposta aceita ,
Aqui está o que você pode e não pode fazer.
O que você pode fazer com um tipo incompleto:
Declare um membro como um ponteiro ou uma referência ao tipo incompleto em outro modelo de classe:
Declare um membro como um ponteiro ou uma referência a uma de suas instanciações incompletas:
Declarar modelos de função ou modelos de função de membro que aceitam / retornam tipos incompletos:
Declarar funções ou funções membro que aceitam / retornam uma de suas instanciações incompletas:
Defina modelos de função ou modelos de função de membro que aceitem / retornem ponteiros / referências ao tipo incompleto (mas sem usar seus membros):
Defina funções ou métodos que aceitam / retornam indicadores / referências a uma de suas instanciações incompletas (mas sem usar seus membros):
Use-o como uma classe base de outra classe de modelo
Use-o para declarar um membro de outro modelo de classe:
Definir modelos ou métodos de função usando esse tipo
O que você não pode fazer com um tipo incompleto:
Use uma de suas instanciações como classe base
Use uma de suas instanciações para declarar um membro:
Definir funções ou métodos usando uma de suas instanciações
Use os métodos ou campos de uma de suas instanciações, de fato tentando desreferenciar uma variável com tipo incompleto
Crie instanciações explícitas do modelo de classe
fonte
X
eX<int>
é exatamente a mesma, e apenas a sintaxe de declaração direta difere de maneira substantiva, com todas as linhas de uma resposta, exceto uma, equivalendo apenas a Luc es/X/X<int>/g
? Isso é realmente necessário? Ou eu perdi um pequeno detalhe diferente? É possível, mas eu comparados visualmente algumas vezes e não consigo ver nenhuma ...No arquivo em que você usa apenas ponteiro ou referência a uma classe. E nenhuma função de membro / membro deve ser chamada por meio desses ponteiros / referências.
com
class Foo;
// declaração a termoPodemos declarar membros de dados do tipo Foo * ou Foo &.
Podemos declarar (mas não definir) funções com argumentos e / ou valores de retorno, do tipo Foo.
Podemos declarar membros de dados estáticos do tipo Foo. Isso ocorre porque os membros de dados estáticos são definidos fora da definição de classe.
fonte
Estou escrevendo isso como uma resposta separada, e não apenas um comentário, porque discordo da resposta de Luc Touraille, não com base na legalidade, mas em software robusto e no perigo de erros de interpretação.
Especificamente, tenho um problema com o contrato implícito do que você espera que os usuários da sua interface tenham que saber.
Se você estiver retornando ou aceitando tipos de referência, estará apenas dizendo que eles podem passar por um ponteiro ou referência que, por sua vez, eles podem ter conhecido apenas por meio de uma declaração direta.
Quando você está retornando um tipo incompleto
X f2();
, está dizendo que o chamador deve ter a especificação completa do tipo X. Eles precisam dele para criar o LHS ou o objeto temporário no site da chamada.Da mesma forma, se você aceitar um tipo incompleto, o chamador deverá ter construído o objeto que é o parâmetro. Mesmo se esse objeto foi retornado como outro tipo incompleto de uma função, o site de chamada precisa da declaração completa. ou seja:
Eu acho que existe um princípio importante de que um cabeçalho deve fornecer informações suficientes para usá-lo sem uma dependência que exija outros cabeçalhos. Isso significa que o cabeçalho deve poder ser incluído em uma unidade de compilação sem causar um erro no compilador quando você usa as funções declaradas.
Exceto
Se essa dependência externa é o comportamento desejado . Em vez de usar a compilação condicional, você pode ter um requisito bem documentado para que eles forneçam seu próprio cabeçalho declarando X. Essa é uma alternativa ao uso de #ifdefs e pode ser uma maneira útil de introduzir zombarias ou outras variantes.
A distinção importante são algumas técnicas de modelo nas quais você NÃO deve explicitamente instancia-las, mencionadas apenas para que alguém não fique irritado comigo.
fonte
I disagree with Luc Touraille's answer
Então, escreva um comentário para ele, incluindo um link para uma postagem no blog, se precisar. Isso não responde à pergunta. Se todo mundo pensasse em perguntas sobre como o X funciona, respostas justificadas discordariam dele ou debateriam limites dentro dos quais deveríamos restringir nossa liberdade de usar o X - quase não teríamos respostas reais.A regra geral que sigo não é incluir nenhum arquivo de cabeçalho, a menos que seja necessário. Portanto, a menos que eu esteja armazenando o objeto de uma classe como uma variável membro da minha classe, não o incluirei, apenas utilizarei a declaração de encaminhamento.
fonte
Contanto que você não precise da definição (pense em indicadores e referências), você pode se safar das declarações avançadas. É por isso que você os vê principalmente nos cabeçalhos, enquanto os arquivos de implementação normalmente puxam o cabeçalho para as definições apropriadas.
fonte
Você geralmente desejará usar a declaração direta em um arquivo de cabeçalho de classes quando desejar usar o outro tipo (classe) como membro da classe. Você não pode usar os métodos de classes declaradas a frente no arquivo de cabeçalho porque o C ++ ainda não conhece a definição dessa classe naquele momento. Essa é a lógica que você precisa mover para os arquivos .cpp, mas se estiver usando funções de modelo, reduza-as apenas à parte que usa o modelo e mova essa função para o cabeçalho.
fonte
Suponha que a declaração direta obtenha seu código para compilar (obj é criado). No entanto, a vinculação (criação de exe) não terá êxito, a menos que as definições sejam encontradas.
fonte
class A; class B { A a; }; int main(){}
e deixe-me saber como isso acontece. Claro que não será compilado. Todas as respostas adequadas aqui explicam o porquê e os contextos precisos e limitados nos quais a declaração antecipada é válida. Você escreveu isso sobre algo totalmente diferente.Eu só quero acrescentar uma coisa importante que você pode fazer com uma classe encaminhada não mencionada na resposta de Luc Touraille.
O que você pode fazer com um tipo incompleto:
Defina funções ou métodos que aceitem / retornem ponteiros / referências para o tipo incompleto e encaminhem esses ponteiros / referências para outra função.
Um módulo pode passar por um objeto de uma classe declarada a frente para outro módulo.
fonte
Como Luc Touraille já explicou muito bem onde usar e não usar a declaração direta da classe.
Vou acrescentar a isso porque precisamos usá-lo.
Devemos usar a declaração Forward sempre que possível para evitar a injeção de dependência indesejada.
Como os
#include
arquivos de cabeçalho são adicionados em vários arquivos, portanto, se adicionarmos um cabeçalho em outro arquivo de cabeçalho, ele adicionará injeção de dependência indesejada em várias partes do código-fonte, o que pode ser evitado adicionando#include
cabeçalho nos.cpp
arquivos sempre que possível, em vez de adicionar a outro arquivo de cabeçalho e use a declaração de encaminhamento de classe sempre que possível nos.h
arquivos de cabeçalho .fonte