Чи може шаблон функції члена класу бути віртуальним?


304

Я чув, що шаблони функцій членів класу C ++ не можуть бути віртуальними. Це правда?

Якщо вони можуть бути віртуальними, що є прикладом сценарію, в якому можна було б використовувати таку функцію?


12
Я зіткнувся з подібною проблемою, а також довідався, що бути віртуальним і шаблоном одночасно суперечливо. Моє рішення полягало в тому, щоб написати магію шаблону, яка буде загальною серед похідних класів, і викликати чисту віртуальну функцію, яка виконує спеціалізовану частину. Звичайно, це пов'язано з природою моєї проблеми, тому може не спрацювати в кожному випадку.
Tamás Szelei

Відповіді:


329

Шаблони - це все про те, що генерує код компілятора під час компіляції . Віртуальні функції - це система, яка визначає, яку функцію потрібно викликати під час виконання .

Після того, як система часу роботи зрозуміла, що потрібно буде викликати шаблонізовану віртуальну функцію, компіляція виконана, і компілятор вже не може генерувати відповідний екземпляр. Тому ви не можете мати шаблони віртуальних членів.

Однак є кілька потужних та цікавих методик, що випливають із поєднання поліморфізму та шаблонів, зокрема так званого стирання типу .


32
Я не бачу мовної причини для цього, лише причини реалізації . vtables не є частиною мови - просто стандартний спосіб компіляторів реалізує мову.
gerardw

16
Virtual functions are all about the run-time system figuring out which function to call at run-time- Вибачте, але це досить неправильний шлях до цього, і досить заплутаний. Це просто непрямий характер, і в цьому немає ніякого "з'ясування часу виконання", під час компіляції відомо, що функція, яку потрібно викликати, є тією, на яку вказує n-й покажчик у vtable. "З'ясування" означає, що є перевірки типу і таке, що не так. Once the run-time system figured out it would need to call a templatized virtual function- віртуальна функція чи ні, відомо під час компіляції.
dtech

9
@ddriver: 1. Якщо компілятори бачать void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, то він "знає", яка функція викликається в точці, що cb.f()називається, і не знає, що для vb.f(). Останнє має бути виявлено під час виконання , яка виконує системою . Незалежно від того, чи хочете ви назвати це "з'ясуванням" і чи це більш-менш ефективно, це факти трохи не змінює.
sbi

9
@ddriver: 2. Екземпляри шаблонів (member) функцій - це (member) функції, тому з розміщенням покажчика на такий примірник у vtable взагалі немає проблем. Але які екземпляри шаблону потрібні, відомо лише тоді, коли компілюється абонент, тоді як vtables встановлюються при складанні базового класу та похідних класів. І всі вони складаються окремо. Ще гірше - нові похідні класи можна підключити до запущених систем під час виконання (подумайте, що ваш браузер динамічно завантажує плагін). Навіть вихідний код абонента може довго втрачатися при створенні нового похідного класу.
sbi

9
@sbi: Чому ти робиш припущення на основі мого імені? Я не плутав дженерики та шаблони. Я знаю, що дженерики Java - це суто пробіг. Ви не вичерпно пояснили, чому ви не можете мати шаблони функцій віртуального члена в C ++, але InQsitive зробив. Ви надмірно спростили шаблон і віртуальну механіку до "компіляції часу" проти "часу запуску" і прийшли до висновку, що "ви не можете мати шаблони функцій віртуального члена". Я посилався на відповідь InQsitive, в якій посилається на "C ++ Templates The Complete Guide". Я не вважаю це "маханням рукою". Гарного дня.
Javanator

133

Від шаблонів C ++ Повне керівництво:

Шаблони функцій учасників не можуть бути оголошені віртуальними. Це обмеження накладається тому, що звичайна реалізація механізму виклику віртуальної функції використовує таблицю фіксованого розміру з одним записом на віртуальну функцію. Однак кількість екземплярів шаблону функції члена не фіксується, поки вся програма не буде переведена. Отже, підтримка шаблонів функцій віртуальних членів потребуватиме підтримки цілого нового механізму в компіляторах C ++ і лінкерах. На відміну від цього, звичайні учасники шаблонів класів можуть бути віртуальними, оскільки їх кількість фіксується при екземплярі класу


8
Я думаю, що сьогоднішній компілятор C ++ і лінкери, особливо з підтримкою оптимізації часу зв'язку, повинні мати можливість генерувати необхідні vtables та компенсації під час посилання. То, можливо, ми отримаємо цю функцію в C ++ 2b?
Кай Петцке

33

C ++ не дозволяє функцій учасників віртуального шаблону прямо зараз. Найбільш вірогідною причиною є складність її здійснення. Раджендра дає вагомі причини, чому цього не можна зробити зараз, але це можливо завдяки розумним змінам стандарту. Особливо опрацювати, скільки насправді існує шаблонна функція, і створити vtable здається важким, якщо врахувати місце виклику віртуальної функції. Люди, що користуються стандартами, просто мають багато інших справ, які потрібно зробити зараз, і C ++ 1x - це велика робота і для авторів-компіляторів.

Коли вам потрібна функція шаблонного члена? Я одного разу натрапив на таку ситуацію, коли намагався переробити ієрархію з чисто віртуальним базовим класом. Це був поганий стиль реалізації різних стратегій. Я хотів змінити аргумент однієї з віртуальних функцій на числовий тип, і замість того, щоб перевантажувати функцію-члена і переосмислювати кожне перевантаження у всіх підкласах, я намагався використовувати функції віртуального шаблону (і повинен був з’ясувати, що вони не існують .)


5
@pmr: Віртуальна функція може бути викликана з коду, який навіть не існував під час компіляції функції. Як компілятор визначить, які екземпляри функції (теоретичного) віртуального шаблону створювати для коду, який навіть не існує?
sbi

2
@sbi: Так, окрема компіляція була б величезною проблемою. Я взагалі не знавець компіляторів C ++, тому не можу запропонувати рішення. Як і у випадку з шаблонними функціями, загалом це слід інстанціювати знову у кожному блоці компіляції, правда? Хіба це не вирішило б проблему?
pmr

2
@sbi, якщо ви маєте на увазі динамічне завантаження бібліотек, це загальна проблема з класами / функціями шаблонів, а не лише з методами віртуальних шаблонів.
Дуб

"C ++ не дозволяє [...]" - було б вдячно бачити посилання на стандарт (незалежно від того, оновлений, коли було написано відповідь, або той, що був оновлений через вісім років) ...
Аконкагуа

19

Віртуальні таблиці функцій

Почнемо з деякого тла віртуальних таблиць функцій та того, як вони працюють ( джерело ):

[20.3] Яка різниця між тим, як викликаються функції віртуальних та невіртуальних членів?

Функції невіртуальних членів вирішуються статично. Тобто функція-член вибирається статично (під час компіляції) виходячи з типу вказівника (або посилання) на об'єкт.

Навпаки, функції віртуальних членів вирішуються динамічно (під час виконання). Тобто функція-член вибирається динамічно (під час виконання) на основі типу об'єкта, а не типу вказівника / посилання на цей об’єкт. Це називається "динамічне зв'язування". Більшість компіляторів використовують деякий варіант наступної методики: якщо об’єкт має одну або більше віртуальних функцій, компілятор ставить прихований покажчик в об'єкт, який називається "віртуальний покажчик" або "v-покажчик". Цей v-покажчик вказує на глобальну таблицю під назвою "віртуальна таблиця" або "v-таблиця".

Компілятор створює v-таблицю для кожного класу, що має принаймні одну віртуальну функцію. Наприклад, якщо клас Circle має віртуальні функції для draw () та переміщення () та зміни розміру (), було б точно одна v-таблиця, пов’язана з класом Circle, навіть якщо були об'єкти gazillion Circle та v-pointer кожен із цих об’єктів Circle вказував би на v-таблицю Circle. Сама таблиця v має покажчики на кожну з віртуальних функцій класу. Наприклад, v-таблиця Circle матиме три покажчики: вказівник на Circle :: draw (), вказівник на Circle :: move () та вказівник на Circle :: resize ().

Під час відправки віртуальної функції система запуску виконує v-покажчик об'єкта до v-таблиці класу, а потім слідує відповідному слоту v-таблиці до коду методу.

Накладні витрати на простір для вищезазначеної методики номінальні: додатковий покажчик на об'єкт (але лише для об'єктів, яким потрібно буде робити динамічне прив'язування), плюс додатковий покажчик на метод (але лише для віртуальних методів). Накладні витрати за часом також є досить номінальними: порівняно із звичайним викликом функції, для виклику віртуальної функції потрібні два додаткові вибори (один для отримання значення v-покажчика, другий для отримання адреси методу). Жодна з цих дій виконання не відбувається з невіртуальними функціями, оскільки компілятор вирішує невіртуальні функції виключно під час компіляції залежно від типу покажчика.


Моя проблема, або як я прийшов сюди

Я зараз намагаюся використовувати щось подібне для базового класу куб-файлів з шаблоновими оптимізованими функціями навантаження, які будуть реалізовуватися по-різному для різних типів кубів (деякі зберігаються в пікселях, інші - за зображенням тощо).

Код:

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;

Як би я хотів, щоб це було, але він не збиратиметься завдяки віртуальному шаблону комбо:

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;

Я закінчив переміщення декларації шаблону на рівень класу . Це рішення змусило б програми знати про конкретні типи даних, які вони читали, перш ніж їх читати, що неприпустимо.

Рішення

попередження, це не дуже красиво, але це дозволило мені видалити повторюваний код виконання

1) в базовому класі

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) і в дитячих класах

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

Зверніть увагу, що LoadAnyCube не оголошено в базовому класі.


Ось ще одна відповідь на переповнення стека з вирішенням проблеми: потрібен спосіб усунення віртуального члена шаблону .


1
Я зустрів ту саму ситуацію і зі спадковою структурою масових класів. макроси допомогли.
ZFY

16

Наступний код можна скласти та запустити належним чином, використовуючи MinGW G ++ 3.4.5 у Вікні 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);
}

а вихід:

A:A<string> a
A<--B:B<string> c
A<--B:3

І пізніше я додав новий клас X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

Коли я намагався використовувати клас X в main (), як це:

X x;
x.func2<string>("X x");

g ++ повідомити про наступну помилку:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

Тож очевидно, що:

  • функція віртуального члена може використовуватися в шаблоні класу. Компілятор легко створити vtable
  • Неможливо визначити функцію члена шаблону класу як віртуальну, як ви бачите, важко визначити підпис функції та виділити vtable записи.

19
Шаблон класу може мати функції віртуальних членів. Функція-член може бути не шаблоном функції-члена, а також функцією віртуального члена.
Джеймс Мак-Нілліс

1
він фактично не відповідає Gcc 4.4.3. У моїй системі напевно Ubuntu 10.04
blueskin

3
Це абсолютно відрізняється від того, на що ставили запитання. Тут шаблон базового класу. Я збирав подібні речі раніше. Це складеться і на Visual Studio 2010
ds-bos-msk

14

Ні, вони не можуть. Але:

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

має такий же ефект, якщо все, що ви хочете зробити, це мати спільний інтерфейс і відкласти реалізацію для підкласів.


3
Це відомо як CRTP, якщо комусь цікаво.
Майкл Чой

1
Але це не допомагає для випадків, коли людина має ієрархію класів і хоче мати змогу викликати віртуальні методи покажчиків до базових класів. Ваш Fooпокажчик кваліфікований як Foo<Bar>, він не може вказувати на a Foo<Barf>або Foo<XXX>.
Кай Петцке

@KaiPetzke: Ви не можете побудувати нестримний покажчик, ні. Але ви можете шаблонувати будь-який код, який не повинен знати конкретного типу, який має такий же ефект (принаймні концептуально - очевидно, зовсім інша реалізація).
Том

8

Ні, функції учасників шаблону не можуть бути віртуальними.


9
Моя цікавість: чому? З якими проблемами стикається компілятор при цьому?
WannaBeGeek

1
Вам потрібна декларація за обсягом (принаймні, для того, щоб виправити типи). Стандарт (та мова) вимагає мати декларацію в області застосування ідентифікаторів, які ви використовуєте.
мерзько

4

В інших відповідях запропонована функція шаблону є фасадом і не дає жодної практичної користі.

  • Функції шаблону корисні для написання коду лише один раз, використовуючи різні типи.
  • Віртуальні функції корисні для спільного інтерфейсу для різних класів.

Мова не дозволяє виконувати функції віртуального шаблону, але при вирішенні можливих варіантів є обидві, наприклад, одна реалізація шаблону для кожного класу та віртуальний загальний інтерфейс.

Однак для кожної комбінації типів шаблону необхідно визначити фіктивну функцію віртуального обгортки:

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

Вихід:

Площа квадрата - 1, площа кола - 3.1415926535897932385

Спробуйте тут


3

Щоб відповісти на другу частину питання:

Якщо вони можуть бути віртуальними, що є прикладом сценарію, в якому можна було б використовувати таку функцію?

Це нерозумно хочеться робити. Наприклад, Java (де кожен метод є віртуальним) не має проблем із загальними методами.

Один із прикладів C ++ бажання шаблону віртуальної функції - функція-член, яка приймає загальний ітератор. Або функція члена, яка приймає об'єкт загальної функції.

Рішення цієї проблеми полягає у використанні стирання типу з функцією boost :: any_range та boost ::, яка дозволить вам прийняти загальний ітератор чи функтор без необхідності робити вашу функцію шаблоном.


6
Java generics - синтаксичний цукор для лиття. Вони не такі, як шаблони.
Бріс М. Демпсі

2
@ BriceM.Dempsey: Ви можете сказати, що кастинг - це спосіб, яким Java реалізує дженеріки, а не навпаки ... і, суворо, подання на випадок експіляції у випадку використання є дійсним IMO.
einpoklum

2

Існує рішення щодо методу "віртуального шаблону", якщо набір типів для методу шаблонів відомий заздалегідь.

Щоб показати ідею, у наведеному нижче прикладі використовуються ( intі double) лише два типи .

Там "віртуальний" шаблон методу ( Base::Method) викликає відповідний віртуальний метод (один з Base::VMethod), який, у свою чергу, викликає реалізацію методу шаблону ( Impl::TMethod).

Потрібно лише реалізувати метод шаблону TMethodу похідних реалізаціях ( AImpl, BImpl) та використовувати 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;
}

Вихід:

0
1
2
3

Примітка: Base::Methodнасправді надлишок реального коду ( VMethodможе бути оприлюднений та використаний безпосередньо). Я додав його, щоб він виглядав як фактичний метод "віртуального" шаблону.


Я придумав це рішення під час вирішення проблеми на роботі. Це схоже на вищезазначене Марка Есселя, але, сподіваюся, краще реалізувати та пояснити.
sad1raf

Я б уже кваліфікував це як обфускування коду, і ви все одно не обходите те, що вам доведеться змінювати початковий Baseклас кожного разу, коли потрібно викликати функцію шаблону з типом аргументу, не сумісним з тими, що реалізовані дотепер. Уникнути цієї необхідності є намір шаблонів ...
Аконкагуа,

Підхід Essel зовсім інший: звичайні віртуальні функції, що приймають різні екземпляри шаблону, - а функція остаточного шаблону у похідному класі служить лише для уникнення дублювання коду і навіть не має протилежного складу в базовому класі ...
Aconcagua,

2

Хоча більш старе питання, на яке відповіли багато людей, я вважаю, що стислим методом, не таким відмінним від інших, що публікуються, є використання другорядного макросу для полегшення дублювання оголошень класу.

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

Отже, тепер для реалізації нашого підкласу:

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

Перевага тут полягає в тому, що, додаючи недавно підтримуваний тип, все це можна зробити з абстрактного заголовка і, можливо, виправити його у кількох вихідних / заголовкових файлах.


0

Принаймні, з gcc 5.4 віртуальні функції можуть бути членами шаблону, але повинні бути самими шаблонами.

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

Виходи

mix before a2
Process finished with exit code 0

0

Спробуйте це:

Напишіть у класі :.

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

Перевірте, чи працюєте з цим, щоб написати цей код у main.cpp:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.