usando modelo externo (C ++ 11)

116

Figura 1: modelos de função

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Esta é a maneira correta de usar extern templateou devo usar essa palavra-chave apenas para modelos de classe, como na Figura 2?

Figura 2: modelos de classe

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Eu sei que é bom colocar tudo isso em um arquivo de cabeçalho, mas se instanciarmos modelos com os mesmos parâmetros em vários arquivos, teremos várias definições iguais e o compilador removerá todos (exceto um) para evitar erros. Como faço para usar extern template? Podemos usá-lo apenas para classes ou podemos usá-lo para funções também?

Além disso, a Figura 1 e a Figura 2 podem ser expandidas para uma solução em que os modelos estão em um único arquivo de cabeçalho. Nesse caso, precisamos usar a extern templatepalavra-chave para evitar várias instanciações iguais. Isso é apenas para classes ou funções também?

codekiddy
fonte
3
Este não é o uso correto de modelos externos de forma alguma ... isso nem mesmo compila
Dani
Você poderia dedicar algum tempo para formular a (uma) pergunta de forma mais clara? Para que você está postando o código? Não vejo uma pergunta relacionada a isso. Além disso, extern template class foo<int>();parece um erro.
ver
@Dani> compila muito bem no meu Visual Studio 2010, exceto a mensagem de aviso: Aviso 1 aviso C4231: extensão não padrão usada: 'extern' antes da instanciação explícita do modelo
codekiddy
2
@sehe pergunta é muito simples: como e quando usar a palavra-chave externa do template? (modelo externo é C ++ 0x new future btw) você disse "Além disso, classe de modelo externo foo <int> (); parece um erro." não, não é, eu tenho um novo livro C ++ e esse é um exemplo do meu livro.
codekiddy de
1
@codekiddy: então o visual studio é realmente estúpido .. no segundo o protótipo não combina com a implementação, e mesmo se eu consertar isso diz 'id não qualificado esperado' perto ()da linha externa. seu livro e estúdio visual estão errados, tente usar um compilador compatível com o padrão, como g ++ ou clang, e você verá o problema.
Dani

Respostas:

181

Você só deve usar extern templatepara forçar o compilador a não instanciar um modelo quando souber que ele será instanciado em outro lugar. É usado para reduzir o tempo de compilação e o tamanho do arquivo de objeto.

Por exemplo:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Isso resultará nos seguintes arquivos de objeto:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Se os dois arquivos estiverem vinculados, um void ReallyBigFunction<int>()será descartado, resultando em perda de tempo de compilação e tamanho do arquivo de objeto.

Para não perder tempo de compilação e tamanho de arquivo de objeto, existe uma externpalavra - chave que faz com que o compilador não compile uma função de modelo. Você deve usar isso se e somente se souber que é usado no mesmo binário em outro lugar.

Mudando source2.cpppara:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Resultará nos seguintes arquivos de objeto:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Quando ambos estiverem vinculados, o segundo arquivo de objeto usará apenas o símbolo do primeiro arquivo de objeto. Não há necessidade de descarte, tempo de compilação e tamanho de arquivo de objeto desperdiçado

Isso deve ser usado apenas dentro de um projeto, como em momentos em que você usa um modelo como vector<int>várias vezes, você deve usar externem todos, exceto um arquivo de origem.

Isso também se aplica a classes e funções como uma e até mesmo a funções-membro de modelo.

Dani
fonte
2
@codekiddy: Não tenho ideia do que o Visual Studio quer dizer com isso. Você realmente deve usar um compilador mais compatível se deseja que a maior parte do código c ++ 11 funcione bem.
Dani
4
@Dani: a melhor explicação dos templates externos que li até agora!
Pietro
90
"se você souber que é usado no mesmo binário em outro lugar.". Isso não é suficiente nem obrigatório. Seu código está "malformado, nenhum diagnóstico necessário". Você não tem permissão para confiar em uma instanciação implícita de outro TU (o compilador tem permissão para otimizá-lo, de maneira semelhante a uma função embutida). Uma instanciação explícita deve ser fornecida em outra TU.
Johannes Schaub - litb de
32
Gostaria de salientar que essa resposta provavelmente está errada e fui mordido por ela. Felizmente, o comentário de Johannes teve uma série de votos positivos e eu prestei mais atenção a ele desta vez. Só posso supor que a grande maioria dos votantes nesta questão não implementou realmente esses tipos de modelos em várias unidades de compilação (como eu fiz hoje) ... Pelo menos para clang, a única maneira infalível é colocar essas definições de modelo em seu cabeçalho! Esteja avisado!
Steven Lu
6
@ JohannesSchaub-litb, você poderia elaborar um pouco mais ou talvez fornecer uma resposta melhor? Não tenho certeza se entendi completamente suas objeções.
andreee de
48

Wikipedia tem a melhor descrição

No C ++ 03, o compilador deve instanciar um modelo sempre que um modelo totalmente especificado for encontrado em uma unidade de tradução. Se o modelo for instanciado com os mesmos tipos em muitas unidades de tradução, isso pode aumentar drasticamente os tempos de compilação. Não há como evitar isso no C ++ 03, então o C ++ 11 introduziu declarações de modelo externo, análogas às declarações de dados externos.

C ++ 03 tem esta sintaxe para obrigar o compilador a instanciar um modelo:

  template class std::vector<MyClass>;

C ++ 11 agora fornece esta sintaxe:

  extern template class std::vector<MyClass>;

que diz ao compilador para não instanciar o modelo nesta unidade de tradução.

O aviso: nonstandard extension used...

O Microsoft VC ++ costumava ter uma versão não padrão desse recurso há alguns anos (em C ++ 03). O compilador avisa sobre isso para evitar problemas de portabilidade com o código que também precisava ser compilado em diferentes compiladores.

Observe o exemplo na página vinculada para ver se funciona quase da mesma maneira. Você pode esperar que a mensagem desapareça com versões futuras do MSVC, exceto, é claro, ao usar outras extensões de compilador não padrão ao mesmo tempo.

ver
fonte
tnx para sua resposta sehe, então o que isso realmente significa é que o futuro "modelo externo" funciona totalmente para o VS 2010 e podemos simplesmente ignorar o aviso? (usando pragma para ignorar a mensagem, por exemplo) e esteja ciente de que o modelo não é instanciado mais do que no tempo em VSC ++. compilador. obrigado.
codekiddy de
4
"... que diz ao compilador para não instanciar o modelo nesta unidade de tradução ." Eu não acho que isso seja verdade. Qualquer método definido na definição de classe conta como sequencial, portanto, se a implementação STL usar métodos sequenciais para std::vector(com certeza todos usam ), externnão terá efeito.
Andreas Haferburg
Sim, essa resposta é enganosa. Documento da MSFT: "A palavra-chave extern na especialização só se aplica a funções de membro definidas fora do corpo da classe. As funções definidas dentro da declaração de classe são consideradas funções embutidas e são sempre instanciadas." Todas as classes STL no VS (a última verificação foi em 2017), infelizmente, apenas métodos inline.
0kcats de
Isso vale para todas as declarações inline, independentemente de onde surjam, sempre @ 0kcats
ver
@sehe A referência ao Wiki com o exemplo std :: vector e uma referência ao MSVC na mesma resposta faz acreditar que pode haver algum benefício em usar extern std :: vector no MSVC, enquanto não há nenhum até agora. Não tenho certeza se este é um requisito do padrão, talvez outros compiladores tenham o mesmo problema.
0kcats de
7

extern template só é necessário se a declaração do modelo estiver completa

Isso foi sugerido em outras respostas, mas não acho que ênfase suficiente foi dada a isso.

O que isso significa é que, nos exemplos de OPs, o extern templatenão tem efeito porque as definições do modelo nos cabeçalhos estavam incompletas:

  • void f();: apenas declaração, sem corpo
  • class foo: declara o método, f()mas não tem definição

Portanto, eu recomendaria apenas remover a extern templatedefinição nesse caso específico: você só precisa adicioná-los se as classes estiverem completamente definidas.

Por exemplo:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

compilar e visualizar símbolos com nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

resultado:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

e então man nmvemos que Usignifica indefinido, então a definição permaneceu apenas TemplCppcomo desejado.

Tudo isso se resume à troca de declarações completas de cabeçalho:

  • vantagens:
    • permite que o código externo use nosso modelo com novos tipos
    • temos a opção de não adicionar instanciações explícitas se concordarmos com o inchaço do objeto
  • desvantagens:
    • ao desenvolver essa classe, as alterações de implementação do cabeçalho levarão os sistemas de construção inteligente a reconstruir todos os includers, que podem ser muitos arquivos
    • se quisermos evitar o inchaço do arquivo objeto, precisamos não apenas fazer instanciações explícitas (o mesmo que acontece com as declarações de cabeçalho incompletas), mas também adicionar extern templatetodos os inclusores, o que os programadores provavelmente esquecerão de fazer

Outros exemplos são mostrados em: Instanciação explícita do modelo - quando é usado?

Uma vez que o tempo de compilação é tão crítico em grandes projetos, eu recomendo declarações de modelo incompletas, a menos que partes externas absolutamente precisem reutilizar seu código com suas próprias classes personalizadas complexas.

E, nesse caso, eu tentaria primeiro usar o polimorfismo para evitar o problema de tempo de construção e apenas usar modelos se ganhos de desempenho perceptíveis pudessem ser feitos.

Testado no Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
4

O problema conhecido com os modelos é o inchaço do código, que é consequência da geração da definição da classe em cada um dos módulos que invoca a especialização do modelo de classe. Para evitar isso, começando com C ++ 0x, pode-se usar a palavra-chave extern na frente da especialização do modelo de classe

#include <MyClass>
extern template class CMyClass<int>;

A instanciação explícita da classe de modelo deve acontecer apenas em uma única unidade de tradução, de preferência aquela com definição de modelo (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;
damirlj
fonte
0

Se você já usou extern para funções antes, exatamente a mesma filosofia é seguida para modelos. se não, ir embora para funções simples pode ajudar. Além disso, você pode querer colocar o (s) externo (s) no arquivo de cabeçalho e incluir o cabeçalho quando necessário.

qqqqq
fonte