Как вы объявляете интерфейс на C ++?

Как мне настроить класс, представляющий интерфейс? Это просто абстрактный базовый класс?


person Aaron Fischer    schedule 25.11.2008    source источник


Ответы (17)


Чтобы расширить ответ 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
        }
};

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

person Mark Ransom    schedule 25.11.2008
comment
Виртуальный десктуктор ++! Это очень важно. Вы также можете включить чистые виртуальные объявления оператора = и определения конструктора копирования, чтобы компилятор не генерировал их автоматически. - person xan; 25.11.2008
comment
Альтернативой виртуальному деструктору является защищенный деструктор. Это отключает полиморфную деструкцию, которая может быть более уместной в некоторых обстоятельствах. Найдите Принцип № 4 в gotw.ca/publications/mill18.htm. - person Fred Larson; 25.11.2008
comment
Если вы знаете, что не собираетесь удалять класс через базовый класс, вам не нужен виртуальный деструктор. Однако в любом случае делать деструктор виртуальным никогда не вредно (за исключением одного поиска в vtable, о нет!). - person bradtgmurray; 17.07.2009
comment
Еще один вариант - определить чистый виртуальный (=0) деструктор с телом. Преимущество здесь в том, что компилятор теоретически может увидеть, что vtable не имеет допустимых членов, и полностью отбросить его. С помощью виртуального деструктора с телом указанный деструктор может быть вызван (виртуально), например в середине построения через указатель this (когда построенный объект все еще имеет тип Parent), и поэтому компилятор должен предоставить действительную vtable. Так что, если вы явно не вызываете виртуальные деструкторы через this во время построения :), вы можете сэкономить на размере кода. - person Pavel Minaev; 31.12.2009
comment
Сорок лун назад, но в любом случае - @Mark Ransom, вы писали: это позволяет вам передать право владения указателем другой стороне, не раскрывая базовый класс. Вы имели в виду подкласс? Потому что тогда в своем комментарии вы написали: если вы знаете, что не собираетесь удалять класс через базовый класс, вам не нужен виртуальный деструктор. - Извините, если мое замечание не имеет смысла - я новичок в C ++ и пытаюсь извлечь уроки из этих вопросов и ответов. - person Lumi; 11.03.2012
comment
@ Люми, теперь, когда я думаю об этом, ты абсолютно прав. Интерфейс является базовым классом, потому что конкретный класс является производным от него и реализует методы. Я должен это исправить. Дайте себе золотую звезду за то, что вы заметили первым, я уверен, что этот ответ просматривали тысячу раз. - person Mark Ransom; 11.03.2012
comment
@MarkRansom, спасибо за виртуальную золотую звезду. :) Тем временем я понял, что вы, должно быть, имели в виду то, что, как я предполагал, имели в виду, потому что я добрался до конца страницы, где Карлос 'answer поясняет суть вопроса с помощью образца кода. Спасибо за отзыв после такого долгого времени! - Бест, Майкл - person Lumi; 11.03.2012
comment
@PavelMinaev С виртуальным деструктором с телом указанный деструктор может быть вызван (виртуально), например в процессе строительства серьезно? - person curiousguy; 16.08.2012
comment
@curiousguy, да, вы можете вызвать деструктор в середине построения, когда вы throw из конструктора производного класса. - person Mark Ransom; 16.08.2012
comment
Рассмотрим иерархию наследования: Base ‹- Derived‹ - MostDerived. Вы находитесь в производном ctor. Вы вызываете глобальную функцию, которая принимает Base* аргумент, передавая его this. Эта функция может явно вызывать деструктор через указатель, и он должен быть отправлен виртуально. - person Pavel Minaev; 16.08.2012
comment
@PavelMinaev, разве не было бы UB явно уничтожать объект до того, как он завершит построение? Я вижу, что конструктор MostDerived очень недоволен. По крайней мере, при уничтожении через throw остальная часть конструкции не выполняется. - person Mark Ransom; 16.08.2012
comment
К тому времени, как он дойдет до ctor MostDerived, это определенно будет UB, но я не вижу ничего, что могло бы сделать, например. this->~Foo() в ctor UB само собой. Рассмотрим случай, когда вы разрушаете его, а затем никогда не возвращаетесь к ctor (например, просто сидите в бесконечном цикле, принимая и обрабатывая ввод). Насколько я понимаю, внутри этого цикла ваше поведение будет четко определено. - person Pavel Minaev; 17.08.2012
comment
Насколько типично для ответа C ++, что верхний ответ не дает прямого ответа на вопрос (хотя, очевидно, код идеален), вместо этого он оптимизирует простой ответ. - person Tim; 25.08.2012
comment
Не забывайте, что в C ++ 11 вы можете указать ключевое слово override, чтобы разрешить аргумент во время компиляции и проверку типа возвращаемого значения. Например, в объявлении Child virtual void OverrideMe() override; - person Sean; 15.01.2013
comment
@Sean, этот ответ на несколько лет предшествует C ++ 11, но вы хорошо замечаете - спасибо! - person Mark Ransom; 15.01.2013
comment
Я согласен с @PavelMinaev, у деструктора практически нет причин быть виртуальным не в чистом виде. - person Maxim Egorushkin; 16.01.2013
comment
Также обратите внимание, что GCC не может обрабатывать чисто виртуальные деструкторы, и вам необходимо их определить: stackoverflow.com/a/3065223/447490 - person Chris; 22.05.2013
comment
@Chris Я немного смущен замечанием Марка Рэнсомса об оптимизации компилятора и вашим комментарием. Похоже, что первый предлагает использовать чистые виртуальные деструкторы, а второй запрещает. Я что-то неправильно понял? Если нет, есть ли общее решение для этого или это зависит от компилятора? - person Griddo; 12.03.2014
comment
@Griddo Нет, вы правильно поняли. Марк Рэнсом предлагает не определять виртуальный деструктор, пока GCC требует этого. Я действительно не думаю, что есть общее решение для этого, кроме загромождения вашего кода с помощью #ifdef и проверок компилятора, но в этом случае я бы предпочел просто определить его и, возможно, потерять некоторые преимущества оптимизации для некоторых компиляторов. - person Chris; 12.03.2014
comment
@xan Не могли бы вы объяснить причины, по которым вы хотите сделать оператор присваивания и конструктор копирования чисто виртуальными в классе IDemo. - person Zachary Kraus; 10.12.2014
comment
@ZacharyKraus интерфейс должен быть очень маленьким подмножеством класса, который фактически реализует интерфейс; в частности, у него не будет элементов данных. Это почти гарантирует, что сгенерированные компилятором операторы копирования и присваивания будут делать неправильные вещи. Если вы хотите реализовать их самостоятельно, то выбейте себя, но обычно в этом нет необходимости - вы получаете доступ к интерфейсу через указатель, и копии указателя в порядке, пока вы управляете владением. - person Mark Ransom; 10.12.2014
comment
@ZacharyKraus: Что сказал MarkRansom. Если вы объявляете интерфейс таким образом, маловероятно, что автоматически сгенерированные компилятором попытки для них будут правильными. Таким образом, безопаснее явно объявить их чистыми виртуальными самостоятельно и убедиться, что вы не можете случайно попасть в ловушку их использования. - person xan; 10.12.2014
comment
@MarkRansom Теперь я понимаю рассуждения, но у меня все еще есть 2 вопроса, которые я просто не понимаю. Во-первых, компилятор автоматически создает конструктор присваивания и копирования для любого класса, даже если вы никогда не используете их в своем коде. Во-вторых, можете ли вы случайно запустить конструктор присваивания или копирования, когда объект является указателем? - person Zachary Kraus; 17.12.2014
comment
@ZacharyKraus, их присутствие или отсутствие невозможно обнаружить, если вы не позвоните им, поэтому по правилу «как если бы» все могло идти в любом направлении - я подозреваю, что большинство компиляторов этого не делают. Вы можете сгенерировать вызов, например, *a = *b но я допускаю, что это нечасто. - person Mark Ransom; 17.12.2014
comment
Кроме того, это просто не имеет значения, поскольку проверка / копирование интерфейса ничего не делает. - person Deduplicator; 07.01.2016
comment
Внутри Child нужно ли писать OverrideMe() как virtual void? - person Sparker0i; 10.08.2018
comment
@ Sparker0i virtual не является обязательным, поскольку объявление базового класса уже сделало его виртуальным, но хорошая практика - быть последовательной. void по-прежнему нужен. - person Mark Ransom; 10.08.2018
comment
Было бы неплохо, если бы вы упомянули, что деструктор можно сделать чисто виртуальным, если нет другой функции. - person L. F.; 15.06.2019
comment
Что делать с предупреждением о том, что в IDemo нет определений виртуальных методов вне очереди? - person Glinka; 26.06.2019
comment
Чтобы добавить к тому, что сказал @xan, начиная с C ++ 11, я полагаю, вы также можете включить чистое виртуальное объявление конструктора перемещения (и оператор присваивания перемещения, если не используете идиому копирования и обмена). - person gblomqvist; 24.03.2021
comment
@gblomqvist, может быть, даже лучше полностью удалить эти методы. Трудно представить себе случай, когда вы использовали бы указатель интерфейса для перемещения объекта. - person Mark Ransom; 24.03.2021
comment
@MarkRansom Это хороший аргумент. - person gblomqvist; 25.03.2021

Создайте класс с чистыми виртуальными методами. Используйте интерфейс, создав другой класс, который переопределяет эти виртуальные методы.

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

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

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};
person bradtgmurray    schedule 25.11.2008
comment
у вас должен быть деструктор ничего не делать в IDemo, чтобы было определено поведение, которое нужно делать: IDemo * p = new Child; / * что угодно * / delete p; - person Evan Teran; 26.11.2008
comment
Почему метод OverrideMe в классе Child является виртуальным? Это необходимо? - person Cemre Mengü; 01.02.2012
comment
@Cemre - нет, в этом нет необходимости, но и не больно. - person PowerApp101; 12.02.2012
comment
Обычно рекомендуется оставлять ключевое слово «виртуальный» при переопределении виртуального метода. Хотя это и не обязательно, это может сделать код более понятным - в противном случае у вас нет указаний на то, что этот метод может использоваться полиморфно или даже существует в базовом классе. - person Kevin; 04.10.2014
comment
@Kevin За исключением override в C ++ 11 - person keyser; 27.11.2014
comment
Не забывайте о точках с запятой в конце объявлений классов. - person Phylliida; 14.10.2015
comment
Необязательно объявлять Child::OverrideMe() как virtual, @Cemre, потому что IDemo::OverrideMe() неявно делает Child::OverrideMe() virtual. Наличие указания на то, что это виртуальная функция (например, virtual или, что еще лучше, override в C ++ 11 или новее) послужит напоминанием для программистов, а override, в частности, также поможет компилятору проверить, действительно ли он что-то переопределяет. - person Justin Time - Reinstate Monica; 28.07.2019

Вся причина того, что у вас есть особая категория типа интерфейса в дополнение к абстрактным базовым классам в C # / Java - это потому, что C # / Java не поддерживает множественное наследование.

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

person Joel Coehoorn    schedule 25.11.2008
comment
По-прежнему было бы неплохо иметь возможность создавать интерфейсы, чтобы избавить нас от большого количества наборов (виртуальный, = 0, виртуальный деструктор). Также множественное наследование кажется мне действительно плохой идеей, и я никогда не видел, чтобы оно использовалось на практике, но интерфейсы нужны постоянно. К сожалению, сообщество C ++ не будет вводить интерфейсы только потому, что они мне нужны. - person ; 01.08.2012
comment
Ha11owed: У него есть интерфейсы. Они называются классами с чисто виртуальными методами и без реализации методов. - person Miles Rout; 01.01.2013
comment
@ Ha11owed В Java есть некоторые особенности, вызванные отсутствием множественного наследования. Взгляните на java.lang.Thread класс и java.lang.Runnable интерфейс, который существует только потому, что вы не можете расширить Thread вместе с другой базой. Из документации может показаться, что это предусмотрено на ваш выбор, но однажды я был вынужден использовать Runnable из-за отсутствия множественного наследования. - person doc; 15.10.2014
comment
@MilesRout: Я знаю, но все же зачем заставлять меня писать так много шаблонного кода? Также было бы ясно, что вы смотрите на интерфейс, не просматривая все методы и проверяя, что все они являются чисто виртуальными. - person ; 28.11.2014
comment
@doc: java.lang.Thread имеет методы и константы, которые вы, вероятно, не хотите иметь в своем объекте. Что должен делать компилятор, если вы расширяетесь из Thread и другого класса с помощью общедоступного метода checkAccess ()? Вы действительно предпочли бы использовать строго именованные базовые указатели, как в C ++? Это похоже на плохой дизайн, вам обычно нужна композиция, когда вы думаете, что вам нужно множественное наследование. - person ; 28.11.2014
comment
@ Ha11owed это было давно, поэтому я не помню подробностей, но у него были методы и константы, которые я хотел иметь в своем классе, и, что более важно, я хотел, чтобы объект моего производного класса был экземпляром Thread. Множественное наследование может быть плохим дизайном, а также плохой композицией. Все зависит от случая. - person doc; 28.11.2014
comment
@ Ha11owed +1 потому что вам обычно нужна композиция там, где вы думаете, что вам нужно множественное наследование - person Joel Coehoorn; 30.11.2014
comment
В C ++ нет ключевого слова abstract. - person Don Larynx; 24.03.2015
comment
Вся причина, по которой у вас есть специальная категория типа интерфейса в дополнение к абстрактным базовым классам в C # / Java, заключается в том, что C # / Java не поддерживает множественное наследование. - Утверждение, что причина в этом, совершенно ложное. Интерфейсы существуют в Java, чтобы не объединять концепции согласования с контрактом и совместной реализацией. Это распространенное заблуждение среди разработчиков C ++, пытающихся принизить то, что, возможно, является дизайном, более совместимым с чистыми объектно-ориентированными языками. - person Dave; 16.12.2015
comment
Objective-C также поддерживает это через протоколы и идет еще дальше (сначала сбивает с толку), делая реализацию метода протокола необязательной. Это предоставило Objective-C многое из того, что скоро получит C ++ через Concepts. - person Dave; 16.12.2015
comment
@ Дэйв: Правда? Objective-C имеет оценку времени компиляции и шаблоны? - person Deduplicator; 07.01.2016
comment
@Deduplicator - я имею в виду ключевое свойство концепций, заключающееся в том, что они позволяют вызываемому объекту (в вашем случае шаблону) указывать ограничения на API, которые аргумент должен / должен поддерживать там, где эти ограничения выражены в объявлении или определении вызываемого, а не внутри иерархии классов аргумента. Нет, в Objective-C нет шаблонов. Шаблоны в этом обсуждении - это вещи, улучшенные с помощью концепции, в некоторых отношениях аналогичной протоколам Objective-C. Но я уверен, что вы это уже знали. - person Dave; 23.01.2016
comment
затем приведите пример того, что вы называете множественным наследованием, потому что до сих пор кажется, что java может это сделать, но не C ++, java может внедрять много интерфейсов - person fdsfdsfdsfds; 29.03.2018
comment
c ++ может напрямую наследовать от многих классов. java может напрямую наследовать только от одного класса. - person Joel Coehoorn; 29.03.2018

В C ++ нет понятия «интерфейс» как такового. AFAIK, интерфейсы были впервые представлены в Java, чтобы обойти отсутствие множественного наследования. Эта концепция оказалась весьма полезной, и того же эффекта можно достичь в C ++ с помощью абстрактного базового класса.

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

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

Невозможно создать экземпляр абстрактного базового класса, т.е. е. вы не можете объявить объект класса A. Вы можете только унаследовать классы от A, но любой производный класс, не обеспечивающий реализацию foo(), также будет абстрактным. Чтобы перестать быть абстрактным, производный класс должен предоставлять реализации для всех чистых виртуальных функций, которые он наследует.

Обратите внимание, что абстрактный базовый класс может быть больше, чем интерфейс, потому что он может содержать элементы данных и функции-члены, которые не являются чисто виртуальными. Эквивалент интерфейса был бы абстрактным базовым классом без каких-либо данных только с чистыми виртуальными функциями.

И, как указал Марк Рэнсом, абстрактный базовый класс должен предоставлять виртуальный деструктор, как и любой базовый класс, если на то пошло.

person Dima    schedule 25.11.2008
comment
Я бы сказал, больше, чем просто отсутствие множественного наследования, чтобы заменить множественное наследование. Java была разработана таким образом с самого начала, потому что множественное наследование создает больше проблем, чем то, что оно решает. Хороший ответ - person OscarRyz; 25.11.2008
comment
Оскар, это зависит от того, являетесь ли вы программистом на C ++, изучившим Java, или наоборот. :) ИМХО, при разумном использовании, как и почти все в C ++, множественное наследование решает проблемы. Абстрактный базовый класс интерфейса - это пример очень разумного использования множественного наследования. - person Dima; 25.11.2008
comment
@OscarRyz Неправильно. MI создает проблемы только при неправильном использовании. Большинство предполагаемых проблем с MI также возникнет с альтернативными конструкциями (без MI). Когда у людей возникают проблемы с дизайном с помощью MI, это вина MI; если у них есть проблема дизайна с SI, это их собственная вина. Алмаз смерти (повторное наследование) - яркий тому пример. Нападение на МИ - это не чистое лицемерие, но близкое к этому. - person curiousguy; 16.08.2012
comment
Семантически интерфейсы отличаются от абстрактных классов, поэтому интерфейсы Java - это не просто технический обходной путь. Выбор между определением интерфейса или абстрактного класса определяется семантикой, а не техническими соображениями. Давайте представим некоторый интерфейс HasEngine: это аспект, функция, и он может быть применен / реализован очень разными типами (будь то классы или абстрактные классы), поэтому мы определим интерфейс для этого, а не абстрактный класс. - person Marek Stanley; 25.03.2015
comment
@MarekStanley, возможно, вы правы, но я бы хотел, чтобы вы выбрали лучший пример. Мне нравится думать об этом с точки зрения наследования интерфейса, а не наследования реализации. В C ++ вы можете либо наследовать интерфейс и реализацию вместе (публичное наследование), либо наследовать только реализацию (частное наследование). В Java у вас есть возможность наследовать только интерфейс без реализации. - person Dima; 25.03.2015
comment
@curiousguy Это очень хорошо подводит итог. Вообще говоря, правильно использовать множественное наследование труднее, чем правильно использовать одиночное наследование, но простая сложность не означает, что это невозможно. Примером может быть что-то вроде этого: class Button; class Image; class ClickableIcon : public Button, public Image {};. Если они спроектированы правильно, Image предоставит графические функции, а Button предоставит интерактивные функции без перекрытия, которое может вызвать проблемы, в то время как ClickableIcon сам обрабатывает функции, для которых предназначена кнопка. - person Justin Time - Reinstate Monica; 08.04.2016
comment
... Я мог бы сформулировать этот пример намного лучше. Button предоставляет интерактивный код, Image предоставляет графический код, а ClickableIcon предоставляет код, специфичный для него самого, а также любой код, необходимый для объединения двух родительских классов. Пока два класса спроектированы правильно и не имеют пересекающихся имен членов, класс должен быть полностью жизнеспособным и более простым в реализации, чем если бы один или оба были интерфейсом. - person Justin Time - Reinstate Monica; 08.04.2016

Насколько я мог проверить, очень важно добавить виртуальный деструктор. Я использую объекты, созданные с помощью 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() никогда не вызывается.

person Carlos C Soto    schedule 05.03.2012
comment
Лучший ответ на этой странице, поскольку в нем приводится практический компилируемый пример. Ваше здоровье! - person Lumi; 11.03.2012
comment
Testet :: ~ Tester () запускается только тогда, когда объект объявлен с помощью Tester. - person Alessandro L.; 17.01.2013
comment
Фактически, будет вызван деструктор строки privatename, и это все, что будет выделено в памяти. Что касается среды выполнения, когда все конкретные члены класса уничтожаются, то же самое происходит и с экземпляром класса. Я попробовал аналогичный эксперимент с классом Line, который имел две структуры Point, и обнаружил, что обе структуры были разрушены (Ха!) При вызове удаления или возврате из охватывающей функции. valgrind подтвердил 0 утечек. - person Chris Reid; 26.05.2017

Мой ответ в основном такой же, как и другие, но я думаю, что нужно сделать еще две важные вещи:

  1. Объявите виртуальный деструктор в своем интерфейсе или сделайте защищенный не виртуальный, чтобы избежать неопределенного поведения, если кто-то попытается удалить объект типа IDemo.

  2. Используйте виртуальное наследование, чтобы избежать проблем с множественным наследованием. (Когда мы используем интерфейсы, чаще возникает множественное наследование.)

И как другие ответы:

  • Создайте класс с чистыми виртуальными методами.
  • Используйте интерфейс, создав другой класс, который переопределяет эти виртуальные методы.

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

    Or

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

    А также

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }
    
person Rexxar    schedule 25.11.2008
comment
нет необходимости в виртуальном наследовании, поскольку у вас нет элементов данных в интерфейсе. - person Robocide; 03.07.2011
comment
Виртуальное наследование важно и для методов. Без него вы столкнетесь с двусмысленностью с OverrideMe (), даже если один из его «экземпляров» является чисто виртуальным (просто попробовал это сам). - person Knarf Navillus; 01.08.2012
comment
@Avishay_ нет необходимости в виртуальном наследовании, поскольку у вас нет элементов данных в интерфейсе. Неправильно. - person curiousguy; 16.08.2012
comment
Обратите внимание, что виртуальное наследование может не работать в некоторых версиях gcc, например в версии 4.3.3, поставляемой с WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067 - person mMontu; 19.09.2012
comment
-1 за наличие не виртуального защищенного деструктора, извините - person Wolf; 14.03.2014

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

Обновление: есть несколько новых ссылок о полиморфизме на основе ненаследования:

person gnzlbg    schedule 25.06.2013
comment
Наследование TBH гораздо яснее, чем та штука C ++ 11, которая претендует на роль интерфейса, а скорее является клеем для связывания некоторых несовместимых проектов. Пример форм оторван от реальности, а Circle класс - плохой дизайн. В таких случаях следует использовать шаблон Adapter. Извините, если это прозвучит немного жестко, но попробуйте использовать какую-нибудь реальную библиотеку, например Qt, прежде чем делать выводы о наследовании. Наследование значительно облегчает жизнь. - person doc; 15.10.2014
comment
Это совсем не звучит резко. Как пример формы отделен от реальности? Не могли бы вы привести пример (может быть, на идеоне) исправления Circle с использованием шаблона Adapter? Мне интересно увидеть его преимущества. - person gnzlbg; 15.10.2014
comment
OK. Я постараюсь поместиться в эту крохотную коробочку. Прежде всего, вы обычно выбираете библиотеки, такие как MyShape, прежде чем начнете писать собственное приложение, чтобы обезопасить свою работу. Иначе как вы могли знать, что Square там еще нет? Предвидение? Вот почему он оторван от реальности. И на самом деле, если вы решите полагаться на библиотеку MyShape, вы можете адаптироваться к ее интерфейсу с самого начала. В примере с фигурами много глупостей (одна из которых состоит в том, что у вас есть две Circle структуры), но адаптер будет выглядеть примерно так - ›ideone.com/UogjWk - person doc; 15.10.2014
comment
Как я уже упоминал, Qt. Посмотрите этот пример - ›qt-project.org/doc /qt-5/qtwidgets-widgets-scribble-example.html, чтобы увидеть, насколько надежным является наследование в целом с виртуальными методами. И с запрещенным наследованием и с этим интерфейсом C ++ 11 вместо этого, должен ли я реализовывать каждый раз ВСЕ виртуальные методы, которые есть в QWidget? Нужно ли мне делегировать каждый вызов для проверки, например, положения моего настраиваемого виджета? С другой стороны, Qt должна была стать библиотекой только для заголовков, потому что для вызова моих реализаций требовался интерфейс. - person doc; 16.10.2014
comment
Тогда это не оторвано от реальности. Когда компания A покупает компанию B и хочет интегрировать кодовую базу компании B в A, у вас есть две полностью независимые кодовые базы. Представьте, что у каждого есть иерархия форм разных типов. Вы не можете легко объединить их с наследованием и добавить компанию C, и у вас будет огромный беспорядок. Думаю, вам стоит посмотреть этот доклад: youtube.com/watch?v=0I0FD3N5cgM Мой ответ старше, но вы увидите сходство. Вам не нужно все время заново реализовывать все, вы можете предоставить реализацию в интерфейсе и выбрать функцию-член, если она доступна. - person gnzlbg; 16.10.2014
comment
Есть также пара выступлений Шона Пэрента о концептуальном полиморфизме, которые могут быть вам интересны. - person gnzlbg; 16.10.2014
comment
Упомянутый Qt принадлежал 3 компаниям (Trolltech, Nokia, Digia), но ни одна из них не сделала то, что вы говорите. Он оторван от реальности. Приведите мне пример реального программного обеспечения, в котором происходило такое слияние кода. Даже в этом случае вам следует использовать шаблон Adapter. Предоставление реализации в интерфейсе, как в вашем ответе, означает, что я должен предоставить все виртуальные методы, которые в противном случае можно было бы просто унаследовать. - person doc; 16.10.2014
comment
Я просмотрел часть видео, и это абсолютно неверно. Я никогда не использую dynamic_cast, кроме как для отладки. Динамическое приведение означает, что с вашим дизайном что-то не так, а дизайны в этом видео неправильны по дизайну :). Гай даже упоминает Qt, но даже здесь он ошибается - QLayout не наследуется ни от QWidget, ни наоборот! - person doc; 16.10.2014
comment
Я обновил ответ со ссылкой на случай, если вам интересно узнать об этом больше. Все три упомянутые вами компании работали с одной и той же библиотекой. Когда дело обстоит не так, наследование становится огромной проблемой. Примеры компаний показаны в разговоре Шона Родителя о концептуальном полиморфизме. По предоставленным ссылкам содержится больше информации, чем я могу здесь предоставить. - person gnzlbg; 16.10.2014
comment
Я смотрел видео о концептуальном полиморфизме. Хотя этот метод интересен и может найти некоторые приложения, он не может заменить полиморфизм на основе наследования (и этот полиморфизм на основе концепций по-прежнему использует наследование, скрытое только в частных структурах модели). Полиморфизм предназначен не только для вашего внутреннего устройства, но и для того, чтобы приложение могло взаимодействовать с библиотекой. Photoshop - это не библиотека, поэтому он даже не рассматривался в видео. Я не вижу никаких практических функций в полиморфизме на основе conecpt, которых нельзя было бы достичь с помощью более чистого наследования IMO. - person doc; 16.10.2014
comment
Я согласен с тем, что полиморфизм на основе наследования очень прост в использовании в C ++ (эта функция имеет полную языковую поддержку). Например, в C это сделать очень сложно. В Haskell, Rust, Scala ... у вас есть черты и классы типов, которые похожи на языковую поддержку полиморфизма на основе ненаследования. Я использовал его в приложении и остался очень доволен. В основном из-за повышения производительности, которое он мне дал (у меня могут быть векторы каждого типа и вектор интерфейсов, поэтому я использую полиморфизм только там, где он мне действительно нужен). Посмотрим, как это будет развиваться в C ++, люди начали говорить об этом сравнительно недавно ... - person gnzlbg; 16.10.2014
comment
Верно. Проблема в том, что я не понимаю, почему наследование является корнем всех зол. Такое заявление нелепо. - person doc; 17.10.2014
comment
Это утверждение больше похоже на то, что полиморфизм, основанный на наследовании, является корнем всех зол. Поразмыслив еще немного о решении, которое вы предоставили с помощью шаблона адаптера, я вижу преимущество использования вместо этого интерфейса со стиранием типа (например, одного sean-родителя) в том, что ваш интерфейс находится в одном месте, и вы можете расширить его поведение и предоставить значения по умолчанию с помощью функций, не являющихся членами, не являющимися друзьями. Однако интерфейс намного сложнее реализовать. Механизм расширения ... его ADL против не-ADL. Если вы переходите на ADL, вы в основном крадете имена функций из пространств имен пользователей. - person gnzlbg; 18.10.2014
comment
И это, ИМО, плохо, потому что вы хотите разделить сложное программное обеспечение на небольшие логические части, и наследование помогает вам в этом. То, что все виды объектов (в данном конкретном случае - модели) выродились в один класс, меня не убеждает. Как мне расширить такой концептуальный интерфейс, если у меня нет доступа к object_t внутренним компонентам и я не могу наследовать после него? Насколько я понимаю, мне нужно реализовать draw() функцию с нуля самостоятельно, и я не привязан к concept_t интерфейсу. Кроме того, с виртуальными методами у меня могут быть реализации по умолчанию, которые я могу переопределить или нет. - person doc; 18.10.2014
comment
Термин «интерфейс» пришел из мира Java. Что говорится в документе Java: реализация интерфейса позволяет классу стать более формальным в отношении поведения, которое он обещает обеспечить. Интерфейсы образуют контракт между классом и внешним миром, и этот контракт применяется во время сборки компилятором. Если ваш класс утверждает, что реализует интерфейс, все методы, определенные этим интерфейсом, должны появиться в его исходном коде, прежде чем класс будет успешно скомпилирован. (docs.oracle.com/javase/tutorial/java/concepts/interface .html) То, что вы представляете, похоже на перевернутый мир. - person doc; 18.10.2014
comment
Совершенно верно, и я думаю, что это правильно: сначала напишите свой класс, а потом заставьте его моделировать интерфейс. Он полностью отделяет реализацию вашего класса от моделирования данного интерфейса и позволяет вам использовать свой класс в контексте, о котором вы не могли подумать в момент его написания. Таким образом работают концепции C ++, классы типов haskell, черты rust / scala и шаблон адаптера, который вы показали. Те же рассуждения лежат в основе идиомы NVI, но эти другие решения еще больше отделяют дизайн, поскольку класс больше не является интерфейсом (в смысле наследования). - person gnzlbg; 18.10.2014
comment
Это путь к беспорядку. Шаблон адаптера существует для привязки несовместимых интерфейсов, если это необходимо, но уже существующих интерфейсов. Я не знаю, зачем даже использовать классы с подходом, сначала напишите свой класс. Здесь предлагается вернуться к чистому C. На каких предпосылках вы строите свой класс, если не знаете, как он будет взаимодействовать с внешним миром? Скажем, вы использовали свою собственную реализацию gnzlbg::vector в своем классе, и я предоставляю интерфейс с моим пользовательским doc::vector, и вам нужно преобразовать ввод и вывод в свои внутренние компоненты. Где повторное использование и эффективность нашего кода, если вам нужно их преобразовать? - person doc; 18.10.2014
comment
Во-первых, обратите внимание, что интерфейс может заключать тип в оболочку либо по значению, либо по уникальной ссылке, либо по общей ссылке. Во-вторых, эта проблема с вектором уже существует, поскольку распределитель находится в типе std :: vector. Решение состоит в том, чтобы либо преобразовать (что включает копирование: дорого), либо вместо этого предоставить интерфейс итератора, используя, например, Boost.Iterator.any_iterator (который использует стирание типов, описанный здесь, и работает медленно). Почему вы говорите, что он просит вернуться к чистому C? Интерфейсы безопасны по типу и могут быть очень тонкими. Я создаю свои классы, зная, что мои знания о моей проблеме (и моих потребностях) будут меняться со временем. - person gnzlbg; 19.10.2014
comment
Каким будет решение на основе наследования, если вы не можете изменить мой код? Лучшее, что я могу придумать, это: иметь чистый вектор виртуального базового класса, сделать свой унаследованный от него (всегда платить vtable), сделать вашу векторную функцию виртуальной (всегда платить эту цену) и использовать шаблон адаптера для моего вектора. Альтернативой может быть создание векторного интерфейса, который адаптирует ваш вектор и мой вектор. И используйте интерфейс, когда вам нужно полиморфно получить доступ к вашему вектору и моему вектору, но только в этих местах. И здесь, когда я говорю «интерфейс», я имею в виду свой или тот, который основан на шаблоне адаптера. - person gnzlbg; 19.10.2014
comment
Позвольте нам продолжить это обсуждение в чате. - person gnzlbg; 19.10.2014
comment
Тип упаковки не помешает вам повторно реализовать ту же функциональность и выполнить очень дорогостоящее преобразование между векторными типами. Проблема с вектором не существует, если мы оба используем либо doc::vector, либо gnzlbg::vector в предпосылке интерфейса. Вы должны научиться правильно использовать наследование, потому что то, что вы сейчас говорите, не имеет никакого смысла! - person doc; 19.10.2014

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

Смущенный?


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

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

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

Основная причина, по которой вы захотите это сделать, заключается в том, что вы хотите предоставить методы интерфейса, как у меня, но сделать их переопределение необязательными.

Чтобы сделать класс интерфейсным классом, требуется чистый виртуальный метод, но все ваши виртуальные методы имеют реализации по умолчанию, поэтому единственный метод, который остается сделать чисто виртуальным, - это деструктор.

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

person Rodyland    schedule 25.11.2008
comment
Почему, ну почему кто-то захочет сделать dtor в этом случае чисто виртуальным? Какая от этого польза? Вы просто навязываете производным классам что-то, что им, вероятно, не нужно включать - dtor. - person Johann Gerell; 26.11.2008
comment
Обновил свой ответ, чтобы ответить на ваш вопрос. Чистый виртуальный деструктор - это допустимый способ создать (единственный способ достичь?) Интерфейсный класс, в котором все методы имеют реализации по умолчанию. - person Rodyland; 02.12.2008

Если вы используете компилятор C ++ от Microsoft, вы можете сделать следующее:

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

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

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

person Mark Ingram    schedule 13.10.2009
comment
Я не совсем понимаю, почему вы использовали novtable вместо стандартного virtual void Bar() = 0; - person Flexo; 17.09.2011
comment
Это в дополнение к (я только что заметил отсутствующий = 0;, который я добавил). Прочтите документацию, если вы не понимаете ее. - person Mark Ingram; 20.09.2011
comment
Я прочитал его без = 0; и предположил, что это просто нестандартный способ сделать то же самое. - person Flexo; 20.09.2011

Вы также можете рассмотреть классы контрактов, реализованные с помощью NVI (Non Virtual Interface Pattern). Например:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1() = default;
    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:
    void do_f(Parameters p) override; // From contract 1.
    void do_g(Parameters p) override; // From contract 2.
};
person Luc Hermitte    schedule 25.11.2008
comment
Для других читателей эта статья доктора Доббса «Беседы: виртуально ваши» Джима Хислопа и Херба Саттера дает более подробное описание. о том, почему можно использовать NVI. - person user2067021; 01.11.2011
comment
А также эту статью «Виртуальность» Херба Саттера. - person user2067021; 01.11.2011

Небольшое дополнение к тому, что там написано:

Во-первых, убедитесь, что ваш деструктор также является чисто виртуальным.

Во-вторых, вы можете захотеть наследовать виртуально (а не обычно) при реализации, просто для хороших мер.

person Uri    schedule 25.11.2008
comment
Мне нравится виртуальное наследование, потому что концептуально это означает, что существует только один экземпляр унаследованного класса. По общему признанию, класс здесь не требует места, поэтому он может быть излишним. Я давно не занимался MI на C ++, но разве невиртуальное наследование не усложнит апкастинг? - person Uri; 25.11.2008
comment
Почему, ну почему кто-то захочет сделать dtor в этом случае чисто виртуальным? Какая от этого польза? Вы просто навязываете производным классам что-то, что им, вероятно, не нужно включать - dtor. - person Johann Gerell; 26.11.2008
comment
Если есть ситуация, что объект будет уничтожен через указатель на интерфейс, вы должны убедиться, что деструктор виртуальный ... - person Uri; 26.11.2008
comment
В чистом виртуальном деструкторе нет ничего плохого. В этом нет необходимости, но в этом нет ничего плохого. Реализация деструктора в производном классе вряд ли станет большим бременем для разработчика этого класса. Смотрите мой ответ ниже, чтобы узнать, почему вы это сделали. - person Rodyland; 05.12.2008
comment
+1 для виртуального наследования, потому что с интерфейсами более вероятно, что класс будет наследовать интерфейс из двух или более путей. Я выбираю защищенные деструкторы в интерфейсах. - person doc; 17.10.2014
comment
@JohannGerell Я считаю, что цель чистых виртуальных деструкторов состоит в том, чтобы предотвратить создание vtable, если только класс, который наследуется от вашего класса интерфейса, сам не унаследован от. Однако, если это намерение, деструктор должен быть объявлен как чистый виртуальный, так и определен следующим образом: class Base { public: ~Base() = 0; }; Base::~Base() {}, чтобы компилятор мог создавать конструкторы по умолчанию для производных классов. - person Justin Time - Reinstate Monica; 08.04.2016

Я все еще новичок в разработке на C ++. Я начал с Visual Studio (VS).

Тем не менее, похоже, никто не упомянул __interface в VS (.NET). Я не уверен, что это хороший способ объявить интерфейс. Но, похоже, он обеспечивает дополнительное обеспечение (упоминается в документы). Таким образом, вам не нужно явно указывать virtual TYPE Method() = 0;, поскольку он будет автоматически преобразован.

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

Однако я не использую его, потому что меня беспокоит кроссплатформенная совместимость компиляции, поскольку он доступен только в .NET.

Если у кого-то есть что-нибудь интересное, поделитесь, пожалуйста. :-)

Спасибо.

person Yeo    schedule 27.09.2015

Хотя верно, что 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 ifs, но всегда сначала измеряйте.
  • Если вы выберете std::function<> вместо указателей на функции, вы можете иметь возможность управлять всеми данными вашего объекта в IBase. С этого момента у вас могут быть схемы значений для IBase (например, std::vector<IBase> будет работать). Обратите внимание, что этот может работать медленнее в зависимости от вашего компилятора и кода STL; также, что текущие реализации std::function<> имеют тенденцию иметь накладные расходы по сравнению с указателями функций или даже виртуальными функциями (это может измениться в будущем).
person lorro    schedule 26.07.2016

В C ++ 20 вы можете использовать concept вместо класса. Это более эффективно, чем наследование.

template <class T>
concept MyInterface = requires (T t) {
    { t.interfaceMethod() };
};

class Implementation {
public:
    void interfaceMethod();
};
static_assert(MyInterface<Implementation>);

Затем вы можете использовать его в функции:

void myFunction(MyInterface auto& arg);

Ограничение в том, что вы не можете использовать его в контейнере.

person Nathan Xabedi    schedule 17.05.2021

Вот определение abstract class в стандарте С ++

n4687

13.4.2

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

person 陳 力    schedule 07.11.2017

Если вам нужна только статическая привязка интерфейса (без виртуального, без экземпляров самого типа интерфейса, интерфейс действует только как руководство):

#include <iostream>
#include <string>

// Static binding interface
// Notice: instantiation of this interface should be usefuless and forbidden.
class IBase {
 protected:
  IBase() = default;
  ~IBase() = default;

 public:
  // Methods that must be implemented by the derived class
  void behaviorA();
  void behaviorB();

  void behaviorC() {
    std::cout << "This is an interface default implementation of bC().\n";
  };
};

class CCom : public IBase {
  std::string name_;

 public:
  void behaviorA() { std::cout << "CCom bA called.\n"; };
};

class CDept : public IBase {
  int ele_;

 public:
  void behaviorB() { std::cout << "CDept bB called.\n"; };
  void behaviorC() {
    // Overwrite the interface default implementation
    std::cout << "CDept bC called.\n";
    IBase::behaviorC();
  };
};

int main(void) {
  // Forbid the instantiation of the interface type itself.
  // GCC error: ‘constexpr IBase::IBase()’ is protected within this context
  // IBase o;

  CCom acom;
  // If you want to use these interface methods, you need to implement them in
  // your derived class. This is controled by the interface definition.
  acom.behaviorA();
  // ld: undefined reference to `IBase::behaviorB()'
  // acom.behaviorB();
  acom.behaviorC();

  CDept adept;
  // adept.behaviorA();
  adept.behaviorB();
  adept.behaviorC();
  // adept.IBase::behaviorC();
}
person rustyhu    schedule 15.07.2021

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 (), а два других класса реализуют ту же функцию, но с другим алгоритмом для вычисления области, специфичной для формы.

person hims    schedule 19.12.2013
comment
Это не то, что считается интерфейсом! Это просто абстрактный базовый класс с одним методом, который нужно переопределить! Интерфейсы обычно являются объектами, которые содержат только определения методов - контракт, который должны выполнять другие классы, когда они реализуют интерфейс. - person guitarflow; 26.03.2014