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?


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.


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.


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.


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>;

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.