Як ви заявляєте інтерфейс на C ++?


Відповіді:


686

Щоб розширити відповідь на bradtgmurray , можливо, ви захочете зробити один виняток із чистого списку віртуальних методів вашого інтерфейсу, додавши віртуальний деструктор. Це дозволяє передати право власності на вказівник іншій стороні без викриття конкретного похідного класу. Деструктору нічого не потрібно робити, оскільки в інтерфейсі немає конкретних членів. Можливо, здається суперечливим визначити функцію як віртуальну, так і вбудовану, але повірте - це не так.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

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


106
Віртуальний дескуктор ++! Це дуже важливо. Ви також можете включити чисті віртуальні декларації оператора = та скопіювати визначення конструктора, щоб запобігти автоматичному генеруванню компілятора.
xan

33
Альтернативою віртуальному деструктору є захищений деструктор. Це вимикає поліморфне руйнування, яке може бути доцільніше в деяких умовах. Шукайте "Настанову №4" у gotw.ca/publications/mill18.htm .
Фред Ларсон

9
Ще один варіант - визначити чистий віртуальний ( =0) деструктор з тілом. Перевага тут полягає в тому, що компілятор теоретично може бачити, що vtable зараз не має дійсних членів, і взагалі відкинути його. У віртуальному деструкторі з тілом зазначений деструктор можна викликати (практично), наприклад, в середині побудови за допомогою thisпокажчика (коли сконструйований об'єкт все ще має Parentтип), і тому компілятор повинен надати дійсну версію. Тож якщо ви не отримаєте явного виклику віртуальних деструкторів через thisбудівництво :), ви можете заощадити на розмірі коду.
Павло Мінаєв

51
Наскільки типово для відповіді C ++, що відповідь на верхню відповідь не відповідає безпосередньо на питання (хоча, очевидно, код ідеальний), натомість оптимізує просту відповідь.
Тім

18
Не забувайте, що в C ++ 11 ви можете вказати overrideключове слово, щоб дозволити аргумент часу компіляції та перевірку типу повернення значення. Наприклад, у декларації про дитинуvirtual void OverrideMe() override;
Шона

245

Складіть клас із чистими віртуальними методами. Використовуйте інтерфейс, створивши інший клас, який перекриває ці віртуальні методи.

Чистий віртуальний метод - метод класу, який визначається як віртуальний і присвоюється 0.

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

29
у вас не повинно бути нічого руйнівника в IDemo, щоб це було визначено для поведінки: IDemo * p = new Child; / * що завгодно * / видалити p;
Еван Теран

11
Чому метод OverrideMe в класі Child є віртуальним? Це потрібно?
Джемре

9
@Cemre - ні, це не потрібно, але і це не шкодить.
PowerApp101

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

27
@Kevin За винятком overrideC ++ 11
клавішник

146

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

C ++ підтримує багаторазове успадкування, тому спеціальний тип не потрібен. Абстрактний базовий клас, що не має абстрактних (чистих віртуальних) методів, функціонально еквівалентний інтерфейсу C # / Java.


17
Ще було б добре створити інтерфейси, щоб врятувати нас від набору стільки (virtual, = 0, virtual destructor). Також багатократне успадкування здається мені дуже поганою ідеєю, і я ніколи не бачив, щоб це використовувалося на практиці, але інтерфейси потрібні весь час. На жаль, комітет C ++ не вводить інтерфейси лише тому, що я їх хочу.
Ha11owed

9
Ha11owed: Він має інтерфейси. Їх називають класами з чистими віртуальними методами та без реалізації методів.
Майлз Рут

6
@doc: java.lang.Thread має методи та константи, які ви, мабуть, не хочете мати у своєму об’єкті. Що робити компілятору, якщо ви перейдете з Thread та іншого класу з відкритим методом checkAccess ()? Ви дійсно вважаєте за краще використовувати сильно названі базові вказівники, як у C ++? Це здається поганим дизайном, вам зазвичай потрібна композиція, де ви думаєте, що вам потрібно багаторазове успадкування.
Ha11owed

4
@ Ha11owed це було давно, тому я не пам'ятаю деталей, але в ньому були методи та контакти, які я хотів мати у своєму класі, і що ще важливіше, я хотів, щоб мій похідний об'єкт класу був Threadпримірником. Багаторазове успадкування може бути поганим дизайном, а також композицією. Все залежить від випадку.
doc

2
@Dave: Дійсно? Objective-C має оцінку часу компіляції та шаблони?
Дедуплікатор

51

У C ++ немає поняття "інтерфейс". AFAIK, інтерфейси вперше були введені на Java, щоб подолати відсутність багаторазового успадкування. Ця концепція виявилася досить корисною, і такого ж ефекту можна досягти в C ++, використовуючи абстрактний базовий клас.

Абстрактний базовий клас - це клас, у якому принаймні один член-функція (метод у Java lingo) є чистою віртуальною функцією, оголошеною за допомогою наступного синтаксису:

class A
{
  virtual void foo() = 0;
};

Абстрактний базовий клас не може бути ініціалізований, тобто ви не можете оголосити об'єкт класу A. Ви можете отримати класи лише з A, але будь-який похідний клас, який не забезпечує реалізацію foo(), також буде абстрактним. Щоб перестати бути абстрактним, похідний клас повинен забезпечити реалізацію для всіх чистих віртуальних функцій, які він успадковує.

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

І, як зазначав Марк Рансом, абстрактний базовий клас повинен мати для цього віртуальний деструктор, як і будь-який базовий клас.


13
Я б сказав більше, ніж "відсутність багаторазового успадкування", щоб замінити множинне спадкування. Java розроблялася так із самого початку, оскільки багатократне успадкування створює більше проблем, ніж те, що вирішує. Хороша відповідь
OscarRyz

11
Оскар, це залежить від того, ви програміст на C ++, який вивчив Java або навпаки. :) IMHO, якщо його використовувати розумно, як майже все, що є в C ++, багатократне успадкування вирішує проблеми. "Інтерфейсний" абстрактний базовий клас є прикладом дуже розумного використання множинної спадщини.
Діма

8
@OscarRyz Неправильно. ІМ створює проблему лише при неправильному використанні. Більшість передбачуваних проблем з ІМ також виникнуть із альтернативними проектами (без ІМ). Коли у людей виникають проблеми в їх дизайні з ІМ, то в ІМ вини; якщо у них проблеми із дизайном із СІ, це їхня вина. "Діамант смерті" (неодноразове успадкування) - яскравий приклад. Зрив МІ - це не чисте лицемірство, а близьке.
curiousguy

4
Семантично інтерфейси відрізняються від абстрактних класів, тому інтерфейси Java - це не просто технічне рішення. Вибір між визначенням інтерфейсу чи абстрактного класу визначається семантикою, а не технічними міркуваннями. Давайте уявимо якийсь інтерфейс "HasEngine": це аспект, особливість, і він може бути застосований до / реалізований дуже різними типами (будь то класи або абстрактні класи), тому ми визначимо для цього інтерфейс, а не абстрактний клас.
Марек Стенлі

2
@MarekStanley, ти можеш мати рацію, але я б хотів, щоб ти вибрав кращий приклад. Мені подобається думати про це з точки зору успадкування інтерфейсу проти успадкування реалізації. У C ++ ви можете або успадкувати і інтерфейс, і реалізацію разом (публічне успадкування), або ви можете успадкувати лише реалізацію (приватне успадкування). У Java у вас є можливість успадкувати лише інтерфейс, без реалізації.
Діма

43

Наскільки я міг протестувати, дуже важливо додати віртуальний деструктор. Я використовую об'єкти, створені newі знищені за допомогою delete.

Якщо ви не додаєте віртуальний деструктор в інтерфейс, то деструктор спадкового класу не викликається.

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

Якщо запустити попередній код без virtual ~IBase() {};, ви побачите, що деструктор Tester::~Tester()ніколи не викликається.


3
Найкраща відповідь на цій сторінці, оскільки це робить сенс, надаючи практичний, складений приклад. Ура!
Лумі

1
Testet :: ~ Tester () працює лише тоді, коли obj "оголошено тестером".
Алессандро Л.

Насправді, деструктор рядка privatename буде викликаний, і в пам'яті це все, для чого буде виділено. Що стосується часу виконання, коли всі конкретні члени класу знищені, так це і екземпляр класу. Я спробував подібний експеримент з класом Line, який мав дві структури Point і виявив, що обидві структури були знищені (Ha!) Після виклику видалення або повернення з охоплюючої функції. valgrind підтвердив 0 витік.
Кріс Рейд

33

Моя відповідь в основному така ж, як і інші, але я думаю, що є ще дві важливі речі:

  1. Оголосіть віртуальний деструктор у своєму інтерфейсі або зробіть захищений невіртуальний, щоб уникнути невизначеного поведінки, якщо хтось намагається видалити об’єкт типу IDemo.

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

І як інші відповіді:

  • Складіть клас із чистими віртуальними методами.
  • Використовуйте інтерфейс, створивши інший клас, який перекриває ці віртуальні методи.

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }
    

    Або

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }
    

    І

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    

2
немає необхідності у віртуальному успадкуванні, оскільки у вас немає членів даних в інтерфейсі.
Робоцид

3
Віртуальна спадщина важлива і для методів. Без цього ви зіткнетесь із двозначностями з OverrideMe (), навіть якщо один з його "екземплярів" є чисто віртуальним (я просто спробував це).
Knarf Navillus

5
@Avishay_ " у віртуальному спадкуванні немає потреби, оскільки у вас немає членів даних в інтерфейсі. " Неправильно.
цікавогут

Зауважте, що віртуальне успадкування може не працювати на деяких версіях gcc, як версія 4.3.3, що постачається з WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu,

-1 за те, що мав невіртуальний захищений деструктор, вибачте
Вовк

10

У C ++ 11 ви можете легко уникнути спадкування взагалі:

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

У цьому випадку Інтерфейс має еталонну семантику, тобто ви повинні переконатися, що об'єкт пережив інтерфейс (можливо також зробити інтерфейси зі значеннями семантики).

Ці інтерфейси мають свої плюси і мінуси:

  • Їм потрібно більше пам’яті, ніж поліморфізм, заснований на спадщині.
  • Вони в цілому швидше , ніж поліморфізм на основі наслідування.
  • У тих випадках, коли ви знаєте остаточний тип, вони набагато швидші! (деякі компілятори, такі як gcc та clang, виконують більше оптимізацій у типах, які не мають / успадковують типи з віртуальними функціями).

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

Скажіть, у мене є програма, в якій я полімерно використовую свої форми за допомогою MyShapeінтерфейсу:

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

У вашій програмі ви робите те ж саме з різними фігурами, використовуючи YourShapeінтерфейс:

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

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

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

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

Оновлення: Є кілька нових посилань про поліморфізм, заснований на спадщині:


5
Спадщина TBH набагато зрозуміліша, ніж та річ C ++ 11, яка видається інтерфейсом, але є скоріше клеєм, який прив'язує деякі непослідовні конструкції. Приклад фігури відмежований від реальності, а Circleклас - поганий дизайн. У Adapterтаких випадках слід використовувати шаблон. Вибачте, якщо це здасться трохи суворим, але спробуйте скористатися якоюсь реальною бібліотекою на зразок, Qtперш ніж приймати судження про спадщину. Спадкування значно полегшує життя.
doc

2
Це зовсім не звучить суворо. Як приклад форми відокремлюється від реальності? Чи можете ви навести приклад (можливо, на ideone) закріплення кола за допомогою Adapterшаблону? Мені цікаво побачити його переваги.
gnzlbg

Гаразд я спробую помістити в цю крихітну скриньку. Перш за все, ви зазвичай вибираєте бібліотеки типу "MyShape" перед тим, як почати писати власну заявку, щоб захистити свою роботу. Інакше як ти міг знати, що Squareйого вже немає? Передбачення? Ось чому вона відірвана від реальності. І насправді, якщо ви вирішите покластися на бібліотеку "MyShape", ви можете використовувати її інтерфейс з самого початку. У прикладі фігур багато дурниць (одна з яких полягає у тому, що у вас є дві Circleструктури), але адаптер виглядав би приблизно так -> ideone.com/UogjWk
doc

2
Тоді воно не відривається від реальності. Коли компанія A купує компанію B і хоче інтегрувати кодову базу компанії B в A, у вас є дві повністю незалежні бази коду. Уявіть, що кожен має ієрархію форм різних типів. Ви не можете легко поєднати їх із спадщиною, і додати компанію C і у вас величезний безлад. Я думаю, ви повинні дивитися цю розмову: youtube.com/watch?v=0I0FD3N5cgM Моя відповідь старша, але ви побачите схожість. Вам не доведеться весь час повторно реалізовувати, ви можете забезпечити реалізацію в інтерфейсі та вибрати функцію члена, якщо вона є.
gnzlbg

1
Я переглянув частину відео, і це абсолютно неправильно. Я ніколи не використовую dynam_cast, за винятком налагодження. Динамічний акторський склад означає, що у вашому дизайні щось не так, і дизайн у цьому відео неправильно розроблений :). Хлопець навіть згадує Qt, але навіть тут він помиляється - QLayout не успадковує від QWidget, ні навпаки!
doc

9

Усі хороші відповіді вище. Ще одне, що слід пам’ятати - ви також можете мати чистий віртуальний деструктор. Єдина відмінність полягає в тому, що вам все-таки потрібно це здійснити.

Плутати?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

Основна причина, яку ви хочете це зробити, - це якщо ви хочете надати методи інтерфейсу, як у мене, але зробити їх переопределенням необов'язковими.

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

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


4
Чому, о, чому б хто хотів зробити dtor в цьому випадку чисто віртуальним? Який би виграш від цього? Ви просто примусите щось до похідних класів, які вони, ймовірно, не повинні включати - dtor.
Йоганн Герелл

6
Оновили свою відповідь, щоб відповісти на ваше запитання. Чистий віртуальний деструктор - це дійсний спосіб досягти (єдиний спосіб досягти?) Класу інтерфейсу, де всі методи мають реалізацію за замовчуванням.
Роділенд

7

Якщо ви використовуєте компілятор C ++ Microsoft, ви можете зробити наступне:

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

Мені подобається такий підхід, оскільки він призводить до набагато меншого коду інтерфейсу, а розмір згенерованого коду може бути значно меншим. Використання novtable видаляє всі посилання на vtable pointer у цьому класі, тому ви ніколи не можете його інстанціювати безпосередньо. Дивіться документацію тут - новинка .


4
Я не дуже розумію, чому ви використовували novtableнад стандартнимиvirtual void Bar() = 0;
Flexo

2
Це на додаток до (я щойно помітив пропущене, = 0;яке я додав). Прочитайте документацію, якщо ви її не розумієте.
Позначити Інграма

Я читав це без, = 0;і припускав, що це просто нестандартний спосіб зробити саме те саме.
Flexo

4

Невелике доповнення до написаного там:

По-перше, переконайтеся, що ваш деструктор також чистий віртуальний

По-друге, ви, можливо, захочете успадковувати практично (а не зазвичай) під час виконання, лише для хороших заходів.


Мені подобається віртуальне успадкування, оскільки концептуально це означає, що є лише один екземпляр спадкового класу. Справді, клас тут не потребує місця, тому він може бути зайвим. Я не робив MI в C ++ деякий час, але чи не невіртуальне успадкування не ускладнить провалля?
Урі

Чому, о, чому б хто хотів зробити dtor в цьому випадку чисто віртуальним? Який би виграш від цього? Ви просто примусите щось до похідних класів, які вони, ймовірно, не повинні включати - dtor.
Йоганн Герелл

2
Якщо виникла ситуація, що об’єкт буде знищений через вказівник на інтерфейс, ви повинні переконатися, що деструктор віртуальний ...
Uri

У чистому віртуальному деструкторі немає нічого поганого. Це не потрібно, але в цьому немає нічого поганого. Реалізація деструктора у похідному класі навряд чи є великим навантаженням на реалізатор цього класу. Дивіться мою відповідь нижче, чому ви робите це.
Роділенд

+1 для віртуального успадкування, оскільки з інтерфейсами більш імовірно, що клас отримає інтерфейс з двох або більше шляхів. Я вибираю захищені деструктори в інтерфейсах Тхо.
doc

4

Ви також можете розглянути класи контрактів, реалізовані за допомогою NVI (Non Virtual Interface Pattern). Наприклад:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

Для інших читачів ця стаття доктора Доббса "Бесіди: практично ваша" Джима Хайслопа та Герба Саттера детальніше розповідає про те, чому можна захотіти використовувати NVI.
user2067021

А також ця стаття «Віртуальність» Герба Саттера.
користувач2067021

1

Я все ще новий в розробці C ++. Я почав з Visual Studio (VS).

Але, схоже, ніхто не згадав про __interfaceVS (.NET) . Я не дуже впевнений, чи це хороший спосіб оголосити інтерфейс. Але це, здається, забезпечує додаткове примусове виконання (згадане в документах ). Таким чином, що вам не потрібно чітко вказувати virtual TYPE Method() = 0;, оскільки він буде автоматично перетворений.

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

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

Якщо у когось є щось цікаве про це, будь ласка, поділіться. :-)

Дякую.


0

Хоча це правда, що virtualце стандарт де-факто для визначення інтерфейсу, не будемо забувати про класичний C-подібний візерунок, який поставляється з конструктором на C ++:

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

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

Поради:

  • Ви можете успадкувати це як базовий клас (віртуальний та невіртуальний дозволені) та заповнити clickконструктор свого нащадка.
  • Ви можете мати вказівник функції в якості protectedчлена і мати publicпосилання та / або отримувач.
  • Як було сказано вище, це дозволяє перемикати реалізацію під час виконання. Таким чином, це також спосіб управління державою. Залежно від кількості ifs в порівнянні зі станом у вашому коді, це може бути швидше, ніж switch()es або ifs (поворот очікується приблизно 3-4 ifс, але завжди вимірюйте спочатку.
  • Якщо ви виберете std::function<>функціональні вказівники, ви, можливо, зможете керувати всіма вашими об'єктними даними IBase. З цього моменту ви можете мати схеми значень для IBase(наприклад, std::vector<IBase>буде працювати). Зауважте, що це може бути повільніше, залежно від вашого компілятора та коду STL; також, що поточні реалізації, std::function<>як правило, мають накладні витрати порівняно з покажчиками функцій або навіть віртуальними функціями (це може змінитися в майбутньому).

0

Ось визначення abstract classстандарту c ++

n4687

13.4.2

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


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

Результат: Площа прямокутника: 35 Площа трикутника: 17

Ми бачили, як абстрактний клас визначав інтерфейс з точки зору getArea () та два інші класи реалізували ту саму функцію, але з різним алгоритмом для обчислення площі, специфічної для форми.


5
Це не те, що вважається інтерфейсом! Це просто абстрактний базовий клас з одним методом, який потрібно перекрити! Інтерфейси, як правило, є об'єктами, які містять лише визначення методів - "контракт", які повинні виконувати інші класи, коли вони реалізують інтерфейс.
гітарний потік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.