Pergunta

Eu tenho o seguinte problema usando instanciação de modelo [*].

arquivo foo.h

class Foo
{
public:
    template <typename F>
    void func(F f)

private:
    int member_;
};

arquivo foo.cc

template <typename F>
Foo::func(F f)
{
     f(member_);
}

arquivo caller.cc

Foo::func(boost::bind(&Bar::bar_func, bar_instance, _1));

Enquanto isso compila bem, o vinculador reclama um símbolo indefinido:

void Foo::func<boost::_bi::bind_t...>

Como posso instanciar a função Foo::func? Desde que leva uma função como argumento, eu estou pouco confuso. Eu tentei adicionar uma função de instanciação em foo.cc , como eu estou acostumado com regulares não-função tipos:

instantiate()
{
    template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}

Obviamente, isso não funciona. Agradecia que se alguém pode me apontar na direção certa.

Obrigado!

[*] Sim, eu li o parashift FAQ Lite.

Foi útil?

Solução

A resposta para isso é compilador dependente. Algumas versões do compilador Sun C ++ iria lidar com isso automaticamente através da construção de um cache de implementações de função modelo que seriam compartilhados entre unidades de tradução separados.

Se você estiver usando Visual C ++, e qualquer outro compilador que não pode fazer isso, você pode muito bem colocar a definição da função no cabeçalho.

Não se preocupe com as definições duplicadas se o cabeçalho é incluído por vários arquivos .cc. As marcas do compilador modelo gerado métodos com um atributo especial para que o vinculador sabe jogar fora duplicatas em vez de reclamar. Esta é uma razão pela qual C ++ tem a "regra de uma definição".

Editar: Os comentários acima se aplicam no caso geral onde seu modelo deve ser capaz de ligar dadas quaisquer parâmetros de tipo. Se você conhece um conjunto fechado de tipos que os clientes usarão, você pode garantir que eles estão disponíveis usando instanciação explícita no arquivo de implementação do modelo, o que fará com que o compilador para gerar definições para outros arquivos para ligação contra. Mas no caso geral onde seu modelo precisa trabalhar com tipos possivelmente apenas conhecidos para o cliente, em seguida, há pouco ponto em separar o modelo em um arquivo de cabeçalho e e arquivo de implementação; qualquer cliente precisa incluir ambas as partes de qualquer maneira. Se você quiser clientes isolar do dependências complexas, ocultar essas dependências atrás de funções não-templated e em seguida, chamar para eles a partir do código do modelo.

Outras dicas

dividindo-o em arquivos como você deseja:
Não que eu recomendo este. Apenas mostrando que é possível.

plop.h

#include <iostream>
class Foo
{
public:
    Foo(): member_(15){}


    // Note No definition of this in a header file.
    // It is defined in plop.cpp and a single instantiation forced
    // Without actually using it.
    template <typename F>
    void func(F f);

private:
    int member_;
};


struct Bar
{
     void bar_func(int val) { std::cout << val << "\n"; }
};

struct Tar
{
    void tar_func(int val) { std::cout << "This should not print because of specialisation of func\n";}
};

Plop.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

template <typename F>
void Foo::func(F f)
{
     f(member_);
}

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Bar, int>, boost::_bi::list2<boost::_bi::value<Bar>, boost::arg<1> (*)()> > myFunc;

// Force the compiler to generate an instantiation of Foo::func()
template void Foo::func<myFunc>(myFunc f);

// Note this is not a specialization as that requires the <> after template.
// See main.cpp for an example of specialization.

main.cpp

#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>

// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Tar, int>, boost::_bi::list2<boost::_bi::value<Tar>, boost::arg<1> (*)()> > myTar;

// Specialization of Foo::func()
template<> void Foo::func<myTar>(myTar f)
{
    std::cout << "Special\n";
}
// Note. This is not instantiated unless it is used.
// But because it is used in main() we get a version.

int main(int argc,char* argv[])
{
    Foo f;
    Bar b;
    Tar t;

    f.func(boost::bind(&Bar::bar_func, b, _1)); // Uses instantiation from plop.cpp
    f.func(boost::bind(&Tar::tar_func, t, _1)); // Uses local specialization
}

Tem incluindo foo.cc em caller.cc. Instanciação é algo que acontece em tempo de compilação -. Quando o compilador vê a chamada na chamada Faz versões instanciado dos modelos, mas precisa ter a definição completa disponível

Eu acho que eles estão ambos referindo é que as definições de função modelo (e não apenas declarações) devem ser incluídos no arquivo, onde eles são usados. funções de modelo na verdade, não existe a menos que / até que eles são usados; se você colocá-los em um arquivo cc separado, então o compilador não sabe sobre eles em outros arquivos cc, a menos que você #include explicitamente que arquivo cc em qualquer arquivo de cabeçalho ou o arquivo que está chamando-los, devido à forma como o analisador obras.

(é por isso que as definições de função de modelo são geralmente mantidos nos arquivos de cabeçalho, como Earwicker descrito.)

Qualquer mais clara?

Eu acredito Earwicker está correto. O problema com instanciar explicitamente a função func membro do molde, neste caso, é que o tipo retornado por impulso :: ligamento é dependente da implementação. É não um boost :: função. A boost :: função pode conter um impulso: bind porque tem um operador de atribuição modelo que deduz o tipo de lado direito (o boost :: resultado ligamento). Neste uso particular de func em caller.cc, com esta implementação particular de impulso, o tipo de boost :: bind é na verdade eles tipo mencionado no erro de vinculador entre o (ie. boost::_bi::bind_t...). Mas instanciar explicitamente func para esse tipo provavelmente terá problemas de portabilidade.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top