Ouvi dizer que os modelos de função de membro da classe C ++ não podem ser virtuais. Isso é verdade?
Se eles podem ser virtuais, qual é o exemplo de um cenário em que alguém usaria essa função?
Ouvi dizer que os modelos de função de membro da classe C ++ não podem ser virtuais. Isso é verdade?
Se eles podem ser virtuais, qual é o exemplo de um cenário em que alguém usaria essa função?
Respostas:
Os modelos são todos sobre o compilador que gera código em tempo de compilação . As funções virtuais são sobre o sistema de tempo de execução, descobrindo qual função chamar no tempo de execução .
Depois que o sistema de tempo de execução descobriu que precisaria chamar uma função virtual de modelo, a compilação está concluída e o compilador não pode mais gerar a instância apropriada. Portanto, você não pode ter modelos de função de membro virtual.
No entanto, existem algumas técnicas poderosas e interessantes decorrentes da combinação de polimorfismo e modelos, principalmente o chamado apagamento de tipo .
Virtual functions are all about the run-time system figuring out which function to call at run-time
- desculpe, mas esta é uma maneira bastante errada e bastante confusa. É apenas uma indireção e não há um "tempo de execução" envolvido, é sabido durante o tempo de compilação que a função a ser chamada é aquela apontada pelo n-ésimo ponteiro na tabela. "Descobrir" implica que há verificações de tipo e coisas do tipo, o que não é o caso. Once the run-time system figured out it would need to call a templatized virtual function
- se a função é virtual ou não, é conhecida no momento da compilação.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
, "saberão" qual função é chamada no ponto cb.f()
chamado e não sabem disso vb.f()
. O último deve ser descoberto em tempo de execução , pelo sistema de tempo de execução . Se você deseja chamar isso de "descobrir" e se é mais ou menos eficiente, isso não muda um pouco esses fatos.
Dos modelos C ++ O guia completo:
Os modelos de função de membro não podem ser declarados virtuais. Essa restrição é imposta porque a implementação usual do mecanismo de chamada de função virtual usa uma tabela de tamanho fixo com uma entrada por função virtual. No entanto, o número de instanciações de um modelo de função de membro não é fixo até que todo o programa tenha sido convertido. Portanto, o suporte a modelos de função de membro virtual exigiria suporte para todo um novo tipo de mecanismo nos compiladores e vinculadores C ++. Por outro lado, os membros comuns dos modelos de classe podem ser virtuais porque seu número é fixo quando uma classe é instanciada
O C ++ não permite funções de membro de modelo virtual no momento. O motivo mais provável é a complexidade de implementá-lo. Rajendra dá uma boa razão para que isso não possa ser feito no momento, mas isso pode ser possível com alterações razoáveis do padrão. Especialmente, descobrir quantas instanciações de uma função modelada realmente existem e a criação da tabela v parece difícil se você considerar o local da chamada da função virtual. As pessoas de padrões apenas têm muitas outras coisas para fazer no momento e o C ++ 1x também é muito trabalhoso para os escritores de compiladores.
Quando você precisaria de uma função de membro modelada? Uma vez me deparei com uma situação em que tentei refatorar uma hierarquia com uma classe base virtual pura. Era um estilo ruim para implementar estratégias diferentes. Eu queria mudar o argumento de uma das funções virtuais para um tipo numérico e, em vez de sobrecarregar a função membro e substituir toda sobrecarga em todas as subclasses, tentei usar funções de modelo virtual (e descobri que elas não existem .)
Vamos começar com algumas informações sobre as tabelas de funções virtuais e como elas funcionam ( fonte ):
[20.3] Qual é a diferença entre como as funções de membro virtual e não virtual são chamadas?
As funções de membro não virtual são resolvidas estaticamente. Ou seja, a função de membro é selecionada estaticamente (em tempo de compilação) com base no tipo de ponteiro (ou referência) para o objeto.
Por outro lado, funções membro virtuais são resolvidas dinamicamente (em tempo de execução). Ou seja, a função de membro é selecionada dinamicamente (em tempo de execução) com base no tipo do objeto, não no tipo do ponteiro / referência a esse objeto. Isso é chamado de "ligação dinâmica". A maioria dos compiladores usa alguma variante da seguinte técnica: se o objeto tiver uma ou mais funções virtuais, o compilador coloca um ponteiro oculto no objeto chamado "ponteiro virtual" ou "ponteiro v". Esse ponteiro v aponta para uma tabela global chamada "tabela virtual" ou "tabela v".
O compilador cria uma tabela em V para cada classe que possui pelo menos uma função virtual. Por exemplo, se a classe Circle tiver funções virtuais para draw () e move () e redimensionar (), haveria exatamente uma tabela-v associada à classe Circle, mesmo que houvesse um zilhão de objetos Circle e o ponteiro-v de cada um desses objetos Circle apontaria para a tabela v Circle. A própria tabela v possui ponteiros para cada uma das funções virtuais da classe. Por exemplo, a tabela v Circle teria três ponteiros: um ponteiro para Circle :: draw (), um ponteiro para Circle :: move () e um ponteiro para Circle :: resize ().
Durante o envio de uma função virtual, o sistema de tempo de execução segue o ponteiro v do objeto para a tabela v da classe e, em seguida, segue o slot apropriado na tabela v para o código do método.
A sobrecarga de custo de espaço da técnica acima é nominal: um ponteiro extra por objeto (mas apenas para objetos que precisam fazer ligação dinâmica), mais um ponteiro extra por método (mas apenas para métodos virtuais). A sobrecarga de custo de tempo também é bastante nominal: comparada a uma chamada de função normal, uma chamada de função virtual requer dois buscadores extras (um para obter o valor do ponteiro v, um segundo para obter o endereço do método). Nenhuma dessas atividades de tempo de execução ocorre com funções não virtuais, pois o compilador resolve funções não virtuais exclusivamente em tempo de compilação, com base no tipo do ponteiro.
Estou tentando usar algo parecido com isso agora para uma classe base de cubefile com funções de carregamento otimizadas, que serão implementadas de maneira diferente para diferentes tipos de cubos (alguns armazenados por pixel, outros por imagem etc.).
Algum código:
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
O que eu gostaria que fosse, mas não será compilado devido a uma combinação de modelos virtual:
template<class T>
virtual void LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
Acabei movendo a declaração do modelo para o nível da classe . Essa solução teria forçado os programas a saber sobre tipos específicos de dados que liam antes de lê-los, o que é inaceitável.
aviso, isso não é muito bonito, mas me permitiu remover o código de execução repetitiva
1) na classe base
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
2) e nas aulas infantis
void LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }
template<class T>
void LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);
Observe que LoadAnyCube não está declarado na classe base.
Aqui está outra resposta de estouro de pilha com uma solução alternativa : precisa de uma solução alternativa para um membro de modelo virtual .
O código a seguir pode ser compilado e executado corretamente, usando o MinGW G ++ 3.4.5 na Janela 7:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class A{
public:
virtual void func1(const T& p)
{
cout<<"A:"<<p<<endl;
}
};
template <typename T>
class B
: public A<T>
{
public:
virtual void func1(const T& p)
{
cout<<"A<--B:"<<p<<endl;
}
};
int main(int argc, char** argv)
{
A<string> a;
B<int> b;
B<string> c;
A<string>* p = &a;
p->func1("A<string> a");
p = dynamic_cast<A<string>*>(&c);
p->func1("B<string> c");
B<int>* q = &b;
q->func1(3);
}
e a saída é:
A:A<string> a
A<--B:B<string> c
A<--B:3
E depois adicionei uma nova classe X:
class X
{
public:
template <typename T>
virtual void func2(const T& p)
{
cout<<"C:"<<p<<endl;
}
};
Quando tentei usar a classe X em main () assim:
X x;
x.func2<string>("X x");
g ++ relate o seguinte erro:
vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'
Portanto, é óbvio que:
Não, eles não podem. Mas:
template<typename T>
class Foo {
public:
template<typename P>
void f(const P& p) {
((T*)this)->f<P>(p);
}
};
class Bar : public Foo<Bar> {
public:
template<typename P>
void f(const P& p) {
std::cout << p << std::endl;
}
};
int main() {
Bar bar;
Bar *pbar = &bar;
pbar -> f(1);
Foo<Bar> *pfoo = &bar;
pfoo -> f(1);
};
tem o mesmo efeito se tudo o que você quer fazer é ter uma interface comum e adiar a implementação para subclasses.
Foo
ponteiro está qualificado como Foo<Bar>
, não pode apontar para um Foo<Barf>
ou Foo<XXX>
.
Não, as funções de membro do modelo não podem ser virtuais.
Nas outras respostas, a função de modelo proposta é uma fachada e não oferece nenhum benefício prático.
A linguagem não permite funções de modelo virtual, mas com uma solução alternativa é possível ter ambas, por exemplo, uma implementação de modelo para cada classe e uma interface comum virtual.
No entanto, é necessário definir para cada combinação de tipo de modelo uma função de invólucro virtual fictício:
#include <memory>
#include <iostream>
#include <iomanip>
//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
virtual void getArea(float &area) = 0;
virtual void getArea(long double &area) = 0;
};
//---------------------------------------------
// Square
class Square : public Geometry {
public:
float size {1};
// virtual wrapper functions call template function for square
virtual void getArea(float &area) { getAreaT(area); }
virtual void getArea(long double &area) { getAreaT(area); }
private:
// Template function for squares
template <typename T>
void getAreaT(T &area) {
area = static_cast<T>(size * size);
}
};
//---------------------------------------------
// Circle
class Circle : public Geometry {
public:
float radius {1};
// virtual wrapper functions call template function for circle
virtual void getArea(float &area) { getAreaT(area); }
virtual void getArea(long double &area) { getAreaT(area); }
private:
// Template function for Circles
template <typename T>
void getAreaT(T &area) {
area = static_cast<T>(radius * radius * 3.1415926535897932385L);
}
};
//---------------------------------------------
// Main
int main()
{
// get area of square using template based function T=float
std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
float areaSquare;
geometry->getArea(areaSquare);
// get area of circle using template based function T=long double
geometry = std::make_unique<Circle>();
long double areaCircle;
geometry->getArea(areaCircle);
std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
return 0;
}
Resultado:
A área quadrada é 1, a área do círculo é 3,1415926535897932385
Experimente aqui
Para responder à segunda parte da pergunta:
Se eles podem ser virtuais, qual é o exemplo de um cenário em que alguém usaria essa função?
Isso não é uma coisa irracional a se querer fazer. Por exemplo, Java (onde todos os métodos são virtuais) não tem problemas com métodos genéricos.
Um exemplo em C ++ de querer um modelo de função virtual é uma função membro que aceita um iterador genérico. Ou uma função membro que aceita um objeto de função genérico.
A solução para esse problema é usar o tipo apagamento com a função boost :: any_range e boost ::, que permitirá aceitar um iterador ou um functor genérico sem a necessidade de transformar sua função em um modelo.
Existe uma solução alternativa para o 'método de modelo virtual' se um conjunto de tipos para o método de modelo for conhecido antecipadamente.
Para mostrar a ideia, no exemplo abaixo, apenas dois tipos são usados ( int
e double
).
Lá, um método de modelo 'virtual' ( Base::Method
) chama o método virtual correspondente (um dos Base::VMethod
) que, por sua vez, chama a implementação do método de modelo ( Impl::TMethod
).
Só é necessário implementar o método de modelo TMethod
em implementações derivadas ( AImpl
, BImpl
) e uso Derived<*Impl>
.
class Base
{
public:
virtual ~Base()
{
}
template <typename T>
T Method(T t)
{
return VMethod(t);
}
private:
virtual int VMethod(int t) = 0;
virtual double VMethod(double t) = 0;
};
template <class Impl>
class Derived : public Impl
{
public:
template <class... TArgs>
Derived(TArgs&&... args)
: Impl(std::forward<TArgs>(args)...)
{
}
private:
int VMethod(int t) final
{
return Impl::TMethod(t);
}
double VMethod(double t) final
{
return Impl::TMethod(t);
}
};
class AImpl : public Base
{
protected:
AImpl(int p)
: i(p)
{
}
template <typename T>
T TMethod(T t)
{
return t - i;
}
private:
int i;
};
using A = Derived<AImpl>;
class BImpl : public Base
{
protected:
BImpl(int p)
: i(p)
{
}
template <typename T>
T TMethod(T t)
{
return t + i;
}
private:
int i;
};
using B = Derived<BImpl>;
int main(int argc, const char* argv[])
{
A a(1);
B b(1);
Base* base = nullptr;
base = &a;
std::cout << base->Method(1) << std::endl;
std::cout << base->Method(2.0) << std::endl;
base = &b;
std::cout << base->Method(1) << std::endl;
std::cout << base->Method(2.0) << std::endl;
}
Resultado:
0
1
2
3
NB:
Base::Method
é realmente excedente para código real ( VMethod
pode ser tornado público e usado diretamente). Eu o adicionei para que pareça um método de modelo 'virtual' real.
Base
classe original toda vez que precisar chamar uma função de modelo com um tipo de argumento não compatível com os implementados até agora. Evitar essa necessidade é a intenção de modelos ...
Embora uma pergunta mais antiga que tenha sido respondida por muitos, acredito que um método sucinto, não tão diferente dos outros publicados, seja usar uma macro menor para ajudar a facilitar a duplicação de declarações de classe.
// abstract.h
// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
void render(int a, char *b) override { render_internal<char>(a, b); } \
void render(int a, short *b) override { render_internal<short>(a, b); } \
// ...
class Renderable
{
public:
// Then, once for each on the abstract
virtual void render(int a, char *a) = 0;
virtual void render(int a, short *b) = 0;
// ...
};
Então agora, para implementar nossa subclasse:
class Box : public Renderable
{
public:
IMPL_RENDER() // Builds the functions we want
private:
template<typename T>
void render_internal(int a, T *b); // One spot for our logic
};
O benefício aqui é que, ao adicionar um tipo recém-suportado, tudo isso pode ser feito a partir do cabeçalho abstrato e renunciar possivelmente a retificá-lo em vários arquivos de origem / cabeçalho.
Pelo menos com as funções virtuais do gcc 5.4, podem ser membros de modelos, mas devem ser eles próprios.
#include <iostream>
#include <string>
class first {
protected:
virtual std::string a1() { return "a1"; }
virtual std::string mixt() { return a1(); }
};
class last {
protected:
virtual std::string a2() { return "a2"; }
};
template<class T> class mix: first , T {
public:
virtual std::string mixt() override;
};
template<class T> std::string mix<T>::mixt() {
return a1()+" before "+T::a2();
}
class mix2: public mix<last> {
virtual std::string a1() override { return "mix"; }
};
int main() {
std::cout << mix2().mixt();
return 0;
}
Saídas
mix before a2
Process finished with exit code 0
Tente o seguinte:
Escreva em classeder.h:
template <typename T>
class Example{
public:
T c_value;
Example(){}
T Set(T variable)
{
return variable;
}
virtual Example VirtualFunc(Example paraM)
{
return paraM.Set(c_value);
}
Verifique, se estiver trabalhando com isso, para escrever este código em main.cpp:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}