Eu tenho algum código de modelo que eu preferiria ter armazenado em um arquivo CPP em vez de embutido no cabeçalho. Eu sei que isso pode ser feito desde que você saiba quais tipos de modelo serão usados. Por exemplo:
arquivo .h
class foo
{
public:
template <typename T>
void do(const T& t);
};
arquivo .cpp
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Observe as duas últimas linhas - a função de modelo foo :: do é usada apenas com ints e std :: strings; portanto, essas definições significam que o aplicativo será vinculado.
Minha pergunta é - isso é um truque desagradável ou funcionará com outros compiladores / linkers? No momento, estou usando esse código apenas com o VS2008, mas desejarei portar para outros ambientes.
do
como um identificador: ptemplate class foo<int>;template class foo<std::string>;
no final do arquivo .cpp?Respostas:
O problema que você descreve pode ser resolvido definindo o modelo no cabeçalho ou através da abordagem descrita acima.
Eu recomendo a leitura dos seguintes pontos do C ++ FAQ Lite :
Eles entram em muitos detalhes sobre esses (e outros) problemas de modelo.
fonte
Para outros nesta página perguntando qual é a sintaxe correta (como eu) para a especialização explícita de modelos (ou pelo menos no VS2008), é a seguinte ...
No seu arquivo .h ...
E no seu arquivo .cpp
fonte
Este código está bem formado. Você só precisa prestar atenção que a definição do modelo é visível no momento da instanciação. Para citar o padrão, § 14.7.2.4:
fonte
a.cpp
(definindo a funçãoa() {}
) eb.cpp
(definindo a funçãob() { a() }
), isso será vinculado com êxito. Se eu estiver certo, a citação acima parece não se aplicar ao caso típico ... estou errado em algum lugar?inline
functionsinline
. A razão é que, sem uma ABI C ++ padronizada, é difícil / impossível definir o efeito que isso teria.Isso deve funcionar bem em todos os lugares onde os modelos são suportados. A instanciação explícita do modelo faz parte do padrão C ++.
fonte
Seu exemplo está correto, mas não é muito portátil. Também há uma sintaxe um pouco mais limpa que pode ser usada (como apontado por @ namespace-sid).
Suponha que a classe modelada faça parte de alguma biblioteca que deve ser compartilhada. Outras versões da classe de modelo devem ser compiladas? O mantenedor da biblioteca deve antecipar todos os possíveis usos de modelo da classe?
Uma abordagem alternativa é uma pequena variação do que você possui: adicione um terceiro arquivo que é o arquivo de implementação / instanciação do modelo.
arquivo foo.h
arquivo foo.cpp
arquivo foo-impl.cpp
A única ressalva é que você precisa dizer ao compilador para compilar em
foo-impl.cpp
vez defoo.cpp
compilar o último não faz nada.Obviamente, você pode ter várias implementações no terceiro arquivo ou vários arquivos de implementação para cada tipo que você deseja usar.
Isso permite muito mais flexibilidade ao compartilhar a classe de modelo para outros usos.
Essa configuração também reduz o tempo de compilação para classes reutilizadas, porque você não está recompilando o mesmo arquivo de cabeçalho em cada unidade de tradução.
fonte
foo.cpp
) das quais as versões são realmente compiladas (infoo-impl.cpp
) e declarações (infoo.h
). Não gosto que a maioria dos modelos de C ++ seja definida inteiramente em arquivos de cabeçalho. Isso é contrário ao padrão C / C ++ de paresc[pp]/h
para cada classe / namespace / qualquer agrupamento que você usar. As pessoas parecem ainda usar arquivos de cabeçalho monolíticos simplesmente porque essa alternativa não é amplamente usada ou conhecida.h/cpp
par, embora eu tenha que cercar a lista original de instanciações em uma proteção de inclusão, mas ainda assim eu poderia compilar ofoo.cpp
normal. Ainda sou bastante novo em C ++ e gostaria de saber se esse uso misto tem alguma ressalva adicional.foo.cpp
efoo-impl.cpp
. Não#include "foo.cpp"
nofoo-impl.cpp
arquivo; em vez disso, adicione a declaraçãoextern template class foo<int>;
parafoo.cpp
impedir que o compilador instancie o modelo ao compilarfoo.cpp
. Assegure-se de que o sistema de construção crie os dois.cpp
arquivos e transmita os dois arquivos de objeto ao vinculador. Isso tem vários benefícios: a) é clarofoo.cpp
que não há instanciação; b) as alterações no foo.cpp não exigem uma recompilação do foo-impl.cpp.foo.cpp
emfoo_impl.h
efoo-impl.cpp
em apenasfoo.cpp
. Eu também adicionaria typedefs para instanciações defoo.cpp
parafoo.h
, da mesma formausing foo_int = foo<int>;
. O truque é fornecer aos usuários duas interfaces de cabeçalho para uma escolha. Quando o usuário precisa de instanciação predefinida, ele incluifoo.h
, quando o usuário precisa de algo fora de ordem, ele incluifoo_impl.h
.Definitivamente, este não é um truque desagradável, mas esteja ciente do fato de que você precisará fazer isso (a especialização explícita do modelo) para cada classe / tipo que você deseja usar com o modelo fornecido. No caso de MUITOS tipos solicitando instanciação de modelo, pode haver MUITAS linhas no seu arquivo .cpp. Para solucionar esse problema, você pode ter um TemplateClassInst.cpp em cada projeto usado, para ter maior controle sobre os tipos que serão instanciados. Obviamente, essa solução não será perfeita (também conhecida como bala de prata), pois você pode acabar quebrando o ODR :).
fonte
Existe, no padrão mais recente, uma palavra-chave (
export
) que ajudaria a aliviar esse problema, mas não foi implementada em nenhum compilador que eu conheça, além do Comeau.Veja o FAQ-lite sobre isso.
fonte
Essa é uma maneira padrão de definir funções de modelo. Eu acho que existem três métodos que li para definir modelos. Ou provavelmente 4. Cada um com prós e contras.
Defina na definição de classe. Não gosto nada disso, porque acho que as definições de classe são estritamente para referência e devem ser fáceis de ler. No entanto, é muito menos complicado definir modelos na classe do que fora. E nem todas as declarações de modelo estão no mesmo nível de complexidade. Esse método também torna o modelo um modelo verdadeiro.
Defina o modelo no mesmo cabeçalho, mas fora da classe. Esta é a minha maneira preferida na maioria das vezes. Ele mantém sua definição de classe organizada, o modelo permanece um modelo verdadeiro. No entanto, requer nomeação de modelo completo, o que pode ser complicado. Além disso, seu código está disponível para todos. Mas se você precisar que seu código esteja embutido, esse é o único caminho. Você também pode fazer isso criando um arquivo .INL no final das suas definições de classe.
Inclua o header.he Implementation.CPP no seu main.CPP. Eu acho que é assim que é feito. Você não precisará preparar nenhuma pré-instanciação, ela se comportará como um verdadeiro modelo. O problema que tenho com isso é que não é natural. Normalmente, não incluímos e esperamos incluir arquivos de origem. Eu acho que desde que você incluiu o arquivo de origem, as funções do modelo podem ser incorporadas.
Este último método, que foi publicado, está definindo os modelos em um arquivo de origem, assim como o número 3; mas, em vez de incluir o arquivo de origem, instanciamos os modelos para os que precisaremos. Não tenho nenhum problema com esse método e às vezes é útil. Temos um código grande, que não pode se beneficiar da inclusão, basta colocá-lo em um arquivo CPP. E se conhecemos instanciações comuns e podemos predefini-las. Isso nos impede de escrever basicamente a mesma coisa 5, 10 vezes. Este método tem o benefício de manter nosso código proprietário. Mas não recomendo colocar pequenas funções usadas regularmente em arquivos CPP. Como isso reduzirá o desempenho da sua biblioteca.
Observe que não estou ciente das conseqüências de um arquivo obj inchado.
fonte
Sim, essa é a maneira padrão de se
especializarinstanciação explícita de . Como você afirmou, não é possível instanciar este modelo com outros tipos.Editar: corrigido com base no comentário.
fonte
Vamos dar um exemplo, digamos que, por algum motivo, você queira ter uma classe de modelo:
Se você compilar esse código com o Visual Studio, ele funcionará imediatamente. O gcc produzirá um erro de vinculador (se o mesmo arquivo de cabeçalho for usado em vários arquivos .cpp):
É possível mover a implementação para o arquivo .cpp, mas você precisa declarar uma classe como esta -
E então o .cpp ficará assim:
Sem duas últimas linhas no arquivo de cabeçalho - o gcc funcionará bem, mas o Visual studio produzirá um erro:
A sintaxe da classe de modelo é opcional, caso você queira expor a função via exportação .dll, mas isso é aplicável apenas à plataforma Windows - para que test_template.h possa se parecer com o seguinte:
com o arquivo .cpp do exemplo anterior.
No entanto, isso dá mais dor de cabeça ao vinculador, por isso é recomendável usar o exemplo anterior se você não exportar a função .dll.
fonte
Hora de uma atualização! Crie um arquivo embutido (.inl ou provavelmente qualquer outro) e simplesmente copie todas as suas definições nele. Certifique-se de adicionar o modelo acima de cada função (
template <typename T, ...>
). Agora, em vez de incluir o arquivo de cabeçalho no arquivo embutido, faça o contrário. Inclua o arquivo embutido após a declaração da sua classe (#include "file.inl"
).Realmente não sei por que ninguém mencionou isso. Não vejo desvantagens imediatas.
fonte
#include "file.inl"
, o pré-processador cole o conteúdofile.inl
diretamente no cabeçalho. Qualquer que seja o motivo que você queira evitar que a implementação entre no cabeçalho, esta solução não resolve esse problema.template
definições fora de linha . Entendo por que as pessoas querem fazer isso - para obter a maior paridade com declarações / definições que não são do modelo, para manter a declaração da interface arrumada etc. -, mas nem sempre vale a pena. É um caso de avaliar os trade-offs de ambos os lados e escolher o menos ruim . ... até quenamespace class
se torna uma coisa: O [ por favor, seja uma coisa ]Não há nada errado com o exemplo que você deu. Mas devo dizer que acredito que não é eficiente armazenar definições de funções em um arquivo cpp. Entendo apenas a necessidade de separar a declaração e a definição da função.
Quando usada em conjunto com instanciação explícita de classe, a BCCL (Boost Concept Check Library) pode ajudá-lo a gerar o código de função do modelo nos arquivos cpp.
fonte
Nenhuma das opções acima funcionou para mim, então aqui está como você resolveu, minha classe tem apenas 1 método modelado ..
.h
.cpp
isso evita erros do vinculador e não é necessário chamar TemporaryFunction
fonte