Especialização de modelo de um único método de uma classe com modelo


92

Sempre considerando que o seguinte cabeçalho, contendo minha classe modelada, está incluído em pelo menos dois .CPParquivos, este código é compilado corretamente:

template <class T>
class TClass 
{
public:
  void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
  // Do something with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
  // Do something with a vector of int's
}

Mas observe o inline no método de especialização. É necessário para evitar um erro de vinculador (em VS2008 é LNK2005) devido ao método ser definido mais de uma vez. Eu entendo isso porque AFAIK uma especialização de modelo completo é o mesmo que uma definição de método simples.

Então, como faço para remover isso inline? O código não deve ser duplicado em cada uso dele. Eu pesquisei no Google, li algumas perguntas aqui no SO e tentei muitas das soluções sugeridas, mas nenhuma construída com sucesso (pelo menos não no VS 2008).

Obrigado!


4
Por que você deseja remover o inline? Você acha isso esteticamente desagradável? Você acha que isso muda o significado do seu código?
Martin York

1
Porque se esse método fosse "longo" e usado em muitos lugares, eu teria seu código binário copiado em todos os lugares, certo? Tentei explicar isso na pergunta, mas acho que não ficou claro ... :)
Chuim

@Martin: E se a implementação precisar de muitos outros códigos que devem ser incluídos por este cabeçalho em vez do arquivo cpp?
sbi

Respostas:


71

Assim como acontece com funções simples, você pode usar declaração e implementação. Coloque sua declaração de cabeçalho:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

e coloque a implementação em um de seus arquivos cpp:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Não se esqueça de remover inline (esqueci e pensei que essa solução não funcionaria :)). Verificado em VC ++ 2005


Eu tentei algo pelo menos semelhante a isso antes, mas encontrei outros erros, mas agora que você mencionou, devo ter esquecido de remover o inlineenquanto copia / cola. Assim funcionou!
Chuim

O mesmo se aplica a funções livres de template (em oposição aos métodos de classe). Eu estava recebendo o mesmo erro de vinculador para minha especialização de função. Mudei o corpo da especialização de função para um arquivo .cpp e deixei a declaração da especialização no cabeçalho e tudo funcionou. Obrigado!
aldo

Acabei de encontrar esse problema, e o que foi descrito acima resolveu para mim. Além disso, você precisa cuidar de onde o compilador expande o código do modelo. Se isso for feito duas vezes, o compilador reclamará de várias definições.
Diederik

4

Você precisa mover a definição de especialização para o arquivo CPP. A especialização da função de membro da classe de modelo é permitida mesmo se a função não for declarada como modelo.


3

Não há razão para remover a palavra-chave inline.
Isso não altera o significado do código de forma alguma.


Copiado do comentário da pergunta: Porque se esse método fosse "longo" e usado em muitos lugares, eu teria seu código binário copiado em todos os lugares, certo? Tentei explicar isso na pergunta, mas acho que não ficou claro ... :)
Chuim

1
Não. O vinculador remove todas as cópias extras. Portanto, em um aplicativo ou biblioteca, você teria apenas uma instância do método.
Martin York

3
Se a inlinepalavra - chave resultar na função realmente inline (o padrão diz que o compilador deve interpretá-la como uma dica), então essas cópias extras não podem ser removidas. É, no entanto, apenas uma dica para embutir (seu efeito principal é dizer "não gere erros em colisões de links de uma maneira particular")
Yakk - Adam Nevraumont 01 de

2

Se você deseja remover o inline por qualquer motivo, a solução de maxim1000 é perfeitamente válida.

Em seu comentário, entretanto, parece que você acredita que a palavra-chave inline significa que a função com todo o seu conteúdo fica sempre inline, mas AFAIK, que na verdade depende muito da otimização do compilador.

Citando o FAQ do C ++

Existem várias maneiras de designar que uma função está embutida, algumas das quais envolvem a palavra-chave embutida, outras não. Não importa como você designa uma função como embutida, é uma solicitação que o compilador pode ignorar: o compilador pode expandir embutidos alguns, todos ou nenhum dos lugares onde você chama uma função designada como embutidos. (Não desanime se isso parecer irremediavelmente vago. A flexibilidade acima é na verdade uma grande vantagem: permite que o compilador trate funções grandes de maneira diferente das pequenas, além de permitir que o compilador gere um código fácil de depurar se você selecionar as opções corretas do compilador.)

Então, a menos que você saiba que essa função irá realmente inchar seu executável ou a menos que você queira removê-lo do cabeçalho de definição do template por outros motivos, você pode realmente deixá-lo onde está sem nenhum dano


1

Gostaria de acrescentar que ainda há um bom motivo para manter a inlinepalavra-chave lá se você pretende deixar também a especialização no arquivo de cabeçalho.

"Intuitivamente, quando você especializa totalmente algo, ele não depende mais de um parâmetro de modelo - então, a menos que você faça a especialização embutida, você precisa colocá-la em um arquivo .cpp em vez de .h ou você acabará violando a única regra de definição ... "

Referência: https://stackoverflow.com/a/4445772/1294184


0

Este é um pequeno OT, mas pensei em deixar isso aqui para o caso de ajudar alguém. Eu estava pesquisando sobre a especialização de modelos, o que me trouxe até aqui e, embora a resposta de @max1000 esteja correta e, no final das contas, tenha me ajudado a descobrir meus problemas, não achei que fosse muito claro.

Minha situação é um pouco diferente (mas semelhante o suficiente para deixar esta resposta, eu acho) do que os OPs. Basicamente, estou usando uma biblioteca de terceiros com todos os diferentes tipos de classes que definem "tipos de status". O coração desses tipos é simplesmente enums, mas todas as classes herdam de um pai comum (abstrato) e fornecem diferentes funções de utilidade, como sobrecarga de operador e uma static toString(enum type)função. Cada status enumé diferente um do outro e não está relacionado. Por exemplo, um enumtem os campos NORMAL, DEGRADED, INOPERABLE, outro tem AVAILBLE, PENDING, MISSING, etc. Meu software é responsável por gerenciar diferentes tipos de status para diferentes componentes. Acontece que eu queria utilizar as toStringfunções para essesenumclasses, mas como são abstratas, não pude instanciá-las diretamente. Eu poderia ter estendido cada classe que quisesse usar, mas no final decidi criar uma templateclasse, onde typenameseria qualquer status concreto que enumme importasse. Provavelmente algum debate pode ser feito sobre essa decisão, mas eu senti que era muito menos trabalhoso do que estender cada enumclasse abstrata com uma personalizada minha e implementar as funções abstratas. E, claro, no meu código, eu só queria poder chamar .toString(enum type)e fazer com que ele imprima a representação da string disso enum. Uma vez que todos os enums eram totalmente não relacionados, cada um deles tinha seu própriotoStringfunções que (depois de algumas pesquisas que aprendi) tinham que ser chamadas usando a especialização de modelo. Isso me trouxe aqui. Abaixo está um MCVE do que eu tive que fazer para fazer isso funcionar corretamente. E, na verdade, minha solução foi um pouco diferente da de @ maxim1000.

Este é um arquivo de cabeçalho (bastante simplificado) para os enums. Na realidade, cada enumaula foi definida em seu próprio arquivo. Este arquivo representa os arquivos de cabeçalho que são fornecidos a mim como parte da biblioteca que estou usando:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

adicionar esta linha apenas para separar o próximo arquivo em um bloco de código diferente:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

próximo arquivo

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

próximo arquivo

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

e isso resulta em:

BEARS1
TIGERS3

Não tenho ideia se essa é a solução ideal para resolver meu problema, mas funcionou para mim. Agora, não importa quantos tipos de enumeração eu acabe usando, tudo que tenho a fazer é adicionar algumas linhas para o toStringmétodo no arquivo .cpp, e posso usar o toStringmétodo já definido das bibliotecas sem implementá-lo sozinho e sem estender cada um enumclasse que eu quero usar.

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.