Um modelo de função de membro da classe pode ser virtual?


304

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?


12
Enfrentei um problema semelhante e também aprendi que é controverso ser virtual e modelo ao mesmo tempo. Minha solução foi escrever a mágica do modelo que será comum entre as classes derivadas e chamar uma função virtual pura que faz a parte especializada. Obviamente, isso está relacionado à natureza do meu problema, portanto pode não funcionar em todos os casos.
Tamás Szelei 26/10/11

Respostas:


329

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 .


32
Não estou vendo uma razão de linguagem para isso, apenas razões de implementação . vtables não fazem parte da linguagem - apenas a maneira padrão como os compiladores implementam a linguagem.
Gerardw #

16
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.
DTECH

9
@driver: 1. Se os compiladores virem 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.
SBI

9
@ddriver: 2. As instâncias dos modelos de função (membro) são funções (membro), portanto não há nenhum problema em colocar um ponteiro para essa instância na tabela. Mas quais instâncias de modelo são necessárias somente são conhecidas quando o chamador é compilado, enquanto as vtables são configuradas quando a classe base e as classes derivadas são compiladas. E estes são todos compilados separadamente. Pior ainda - novas classes derivadas podem ser vinculadas a sistemas em execução em tempo de execução (pense no seu navegador carregando dinamicamente um plug-in). Até o código-fonte do chamador pode se perder por muito tempo quando uma nova classe derivada é criada.
SBI

9
@bi: Por que você está fazendo suposições com base no meu nome? Não confundi genéricos e modelos. Eu sei que os genéricos de Java são puramente tempo de execução. Você não explicou exaustivamente por que não pode ter modelos de função de membro virtual em C ++, mas o InQsitive tinha. Você simplificou demais o modelo e a mecânica virtual para 'tempo de compilação' versus 'tempo de execução' e concluiu que "não é possível ter modelos de função de membro virtual". Mencionei a resposta do InQsitive, que faz referência a "Modelos C ++, o Guia Completo". Não considero que seja "acenar com a mão". Tenha um bom dia.
Javanator 9/09/16

133

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


8
Eu acho que o compilador e os vinculadores de C ++ de hoje, principalmente com suporte à otimização do tempo de link, devem ser capazes de gerar as vtables e compensações necessárias no momento do link. Então, talvez tenhamos esse recurso em C ++ 2b?
Kai Petzke

33

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 .)


5
@pmr: Uma função virtual pode ser chamada de código que nem existia quando a função foi compilada. Como o compilador determinaria quais instâncias de um membro do modelo virtual (teórico) funcionam para gerar para o código que nem existe?
SBI

2
@bi: Sim, compilação separada seria um grande problema. Não sou especialista em compiladores C ++, portanto não posso oferecer uma solução. Como nas funções de modelo em geral, ele deve ser instanciado novamente em todas as unidades de compilação, certo? Isso não resolveria o problema?
Pmr1

2
@sbi Se você está se referindo ao carregamento dinâmico de bibliotecas, isso é um problema geral com classes / funções de modelo, não apenas com métodos de modelo virtual.
Oak

"C ++ não permite [...]" - gostaria de ver referência ao padrão (não importa se está atualizado quando a resposta foi escrita ou se está atualizado oito anos depois) ...
Aconcagua

19

Tabelas de funções virtuais

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.


Meu problema, ou como eu vim aqui

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.

Solução

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 .


1
Eu conheci a mesma situação e a estrutura de herança das classes de massa. macros ajudaram.
ZFY 4/03

16

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:

  • A função de membro virtual pode ser usada em um modelo de classe. É fácil para o compilador construir o vtable
  • É impossível definir uma função de membro do modelo de classe como virtual, como você pode ver, é difícil determinar a assinatura da função e alocar as entradas vtable.

19
Um modelo de classe pode ter funções membro virtuais. Uma função de membro pode não ser um modelo de função de membro e uma função de membro virtual.
James McNellis

1
ele realmente falha com o gcc 4.4.3. No meu sistema, com certeza Ubuntu 10.04
blueskin

3
Isso é totalmente diferente do que a pergunta foi feita. Aqui toda a classe base é modelada. Eu compilei esse tipo de coisa antes. Isso também seria compilado no Visual Studio 2010
ds-bos-msk

14

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.


3
Isso é conhecido como CRTP, se alguém estiver curioso.
Michael Choi

1
Mas isso não ajuda nos casos em que alguém tem uma hierarquia de classes e deseja chamar métodos virtuais de ponteiros para as classes base. Seu Fooponteiro está qualificado como Foo<Bar>, não pode apontar para um Foo<Barf>ou Foo<XXX>.
Kai Petzke 08/02/19

@KaiPetzke: Você não pode construir um ponteiro irrestrito, não. Mas você pode modelar qualquer código que não precise conhecer o tipo concreto, que tem o mesmo efeito (pelo menos conceitualmente - obviamente uma implementação completamente diferente).
Tom

8

Não, as funções de membro do modelo não podem ser virtuais.


9
Minha curiosidade é: por quê? Quais problemas o compilador enfrenta ao fazer isso?
WannaBeGeek

1
Você precisa de uma declaração no escopo (pelo menos, para obter os tipos corretos). É exigido pelo padrão (e pelo idioma) que tenha uma declaração no escopo dos identificadores que você usa.
dirkgently

4

Nas outras respostas, a função de modelo proposta é uma fachada e não oferece nenhum benefício prático.

  • As funções de modelo são úteis para escrever código apenas uma vez usando tipos diferentes.
  • As funções virtuais são úteis por ter uma interface comum para diferentes classes.

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


3

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.


6
Os genéricos Java são açúcar sintático para conversão. Eles não são iguais aos modelos.
Brice M. Dempsey

2
@ BriceM.Dempsey: Você poderia dizer que a conversão é a maneira como o Java implementa os genéricos, e não o contrário ... e, semelhantemente, o exemplo de caso de uso apresentado é IMO válido.
Einpoklum # 10/16

2

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 ​​( inte 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 TMethodem 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 ( VMethodpode ser tornado público e usado diretamente). Eu o adicionei para que pareça um método de modelo 'virtual' real.


Eu vim com essa solução enquanto resolvia um problema no trabalho. Parece semelhante ao de Mark Essel acima, mas, espero, seja melhor implementado e explicado.
sad1raf 29/03/19

Eu já qualificaria isso como ofuscação de código, e você ainda não contorna o fato de que precisa modificar a Baseclasse 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 ...
Aconcagua

A abordagem de Essels é totalmente diferente: as funções virtuais comuns aceitam instâncias de modelo diferentes - e a função de modelo final na classe derivada serve apenas para evitar a duplicação de código e nem sequer tem uma parte contrária na classe base ...
Aconcagua

2

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.


0

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

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