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


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.