Как вы объявляете интерфейс в 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
Виртуальный desctuctor ++! Это очень важно. Вы также можете включить чисто виртуальные объявления определений operator = и копирования конструктора, чтобы компилятор не генерировал их автоматически.
Xan
33
Альтернативой виртуальному деструктору является защищенный деструктор. Это отключает полиморфное разрушение, которое может быть более подходящим в некоторых обстоятельствах. Ищите «Руководство № 4» в gotw.ca/publications/mill18.htm .
Фред Ларсон
9
Еще один вариант - определить чисто виртуальный ( =0) деструктор с телом. Преимущество здесь в том, что теоретически компилятор может увидеть, что vtable не имеет допустимых членов, и вообще отказаться от него. В случае виртуального деструктора с телом указанный деструктор может быть вызван (виртуально), например, в середине конструкции с помощью thisуказателя (когда сконструированный объект все еще имеет Parentтип), и поэтому компилятор должен предоставить действительную vtable. Поэтому, если вы не вызываете виртуальные деструкторы напрямую 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
        }
};
bradtgmurray
источник
29
у вас должен быть деструктор бездействия в IDemo, чтобы его поведение определялось: IDemo * p = new Child; / * что угодно * / удалить p;
Эван Теран
11
Почему метод OverrideMe в дочернем классе является виртуальным? Это необходимо?
Cemre
9
@ Cemre - нет, это не обязательно, но это тоже не больно.
PowerApp101
11
Как правило, рекомендуется переопределять ключевое слово «виртуальный» при переопределении виртуального метода. Хотя это и не обязательно, это может сделать код более понятным - в противном случае у вас нет никаких указаний на то, что этот метод может использоваться полиморфно или даже существует в базовом классе.
Кевин
27
@Kevin За исключением с overrideв C ++ 11
keyser
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экземпляром. Многократное наследование может быть как плохим дизайном, так и композицией. Все зависит от случая.
док
2
@Dave: Действительно? Objective-C имеет оценку времени компиляции и шаблоны?
Дедупликатор
51

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

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

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

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

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

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

Дима
источник
13
Больше, чем «отсутствие множественного наследования», я бы сказал, чтобы заменить множественное наследование. Java с самого начала создавалась таким образом, потому что множественное наследование создает больше проблем, чем решает. Хороший ответ
ОскарРиз
11
Оскар, это зависит от того, являетесь ли вы программистом C ++, который изучил Java, или наоборот. :) ИМХО, если использовать разумно, как и почти все в C ++, множественное наследование решает проблемы. Абстрактный интерфейсный базовый класс является примером очень разумного использования множественного наследования.
Дима
8
@ OscarRyz Неверно. МИ только создает проблему при неправильном использовании. Большинство предполагаемых проблем с инфарктом миокарда также могут быть альтернативными (без инфаркта миокарда). Когда люди имеют проблемы с их дизайном с MI, это вина MI; если у них есть проблемы с дизайном СИ, это их собственная ошибка. «Алмаз смерти» (повторное наследование) является ярким примером. МИ избиение не является чистым лицемерием, но близко.
любопытный парень
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
Лучший ответ на этой странице, потому что он дает практический, компилируемый пример. Ура!
Lumi
1
Testet :: ~ Tester () запускается, только когда объект "Объявлен с помощью Tester".
Алессандро Л.
На самом деле, будет вызван деструктор строки privatename, и в памяти это все, что там будет выделено. Что касается времени выполнения, когда уничтожаются все конкретные члены класса, то же относится и к экземпляру класса. Я попытался провести аналогичный эксперимент с классом Line, в котором было две структуры Point, и обнаружил, что обе структуры были разрушены (Ха!) После вызова удаления или возврата из охватывающей функции. 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
            }
    }
Rexxar
источник
2
нет необходимости в виртуальном наследовании, поскольку у вас нет элементов данных в интерфейсе.
Robocide
3
Виртуальное наследование важно и для методов. Без этого вы столкнетесь с неоднозначностью с OverrideMe (), даже если один из его «экземпляров» является чисто виртуальным (просто попробовал это сам).
Knarf Navillus
5
@Avishay_ " нет необходимости в виртуальном наследовании, поскольку у вас нет элементов данных в интерфейсе. " Неправильно.
любопытный парень
Обратите внимание, что виртуальное наследование может не работать в некоторых версиях gcc, как в версии 4.3.3, которая поставляется с WinAVR 2010: gcc.gnu.org/bugzilla/show_bug.cgi?id=35067
mMontu
-1 из-за не виртуального защищенного деструктора, извините
Wolf
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, проводят больше оптимизаций в типах, которые не имеют / наследуют от типов с виртуальными функциями).

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

Скажем, у меня есть приложение, в котором я работаю с моими формами полиморфно, используя 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?).

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

gnzlbg
источник
5
Наследование TBH гораздо яснее, чем то, что есть в C ++ 11, которое притворяется интерфейсом, но скорее связывает некоторые противоречивые конструкции. Пример формы оторван от реальности, а Circleкласс - плохой дизайн. Вы должны использовать Adapterшаблон в таких случаях. Извините, если это будет звучать немного резко, но попробуйте использовать некоторую реальную библиотеку, как Qtпрежде, чем выносить суждения о наследовании. Наследование делает жизнь намного проще.
док
2
Это не звучит резко. Как пример формы оторван от реальности? Не могли бы вы привести пример (возможно, на ideone) исправления круга с помощью Adapterшаблона? Мне интересно увидеть его преимущества.
gnzlbg
Хорошо, я постараюсь вписаться в эту крошечную коробочку. Прежде всего, вы обычно выбираете библиотеки типа «MyShape» перед тем, как начать писать свое собственное приложение, чтобы обезопасить свою работу. Иначе откуда ты мог знать, что Squareего там еще нет? Предвидение? Вот почему это оторвано от реальности. И на самом деле, если вы решите положиться на библиотеку «MyShape», вы можете использовать ее интерфейс с самого начала. В примере фигур есть много глупостей (одна из которых состоит в том, что у вас есть две Circleструктуры), но адаптер будет выглядеть примерно так -> ideone.com/UogjWk
doc
2
Тогда это не оторвано от реальности. Когда компания A покупает компанию B и хочет интегрировать кодовую базу компании B в A, у вас есть две совершенно независимые базы кода. Представьте, что у каждого есть иерархия Shape разных типов. Вы не можете легко объединить их с наследованием и добавить компанию C, и у вас огромный беспорядок. Я думаю, вам следует посмотреть этот доклад: youtube.com/watch?v=0I0FD3N5cgM Мой ответ старше, но вы увидите сходство. Вам не нужно все время переопределять, вы можете предоставить реализацию в интерфейсе и выбрать функцию-член, если она доступна.
gnzlbg
1
Я смотрел часть видео, и это совершенно неправильно. Я никогда не использую dynamic_cast, кроме как для целей отладки. Динамическое приведение означает, что что-то не так с вашим дизайном, а дизайны в этом видео неправильны по дизайну :). Парень даже упоминает Qt, но даже здесь он не прав - QLayout не наследует ни от QWidget, ни наоборот!
док
9

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

Смущенный?


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

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

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

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

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

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

Rodyland
источник
4
Почему, ну почему кто-то хочет сделать dtor в этом случае чисто виртуальным? Какую выгоду это принесет? Вы просто навязали бы что-то на производные классы, которые они, вероятно, не должны включать - dtor.
Иоганн Герелл
6
Обновил мой ответ, чтобы ответить на ваш вопрос. Чистый виртуальный деструктор является правильным способом достижения (единственный способ достичь?) Класса интерфейса, где все методы имеют реализации по умолчанию.
Rodyland
7

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

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

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

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

Марк Инграм
источник
4
Я не совсем понимаю, почему вы использовали novtableпо стандартуvirtual void Bar() = 0;
Flexo
2
Это в дополнение к (я только что заметил недостающее, = 0;которое я добавил). Прочтите документацию, если вы ее не понимаете.
Марк Ингрэм
Я прочитал это без = 0;и предположил, что это был просто нестандартный способ сделать то же самое.
Flexo
4

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

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

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

Uri
источник
Мне нравится виртуальное наследование, потому что концептуально это означает, что существует только один экземпляр унаследованного класса. По общему признанию, у класса здесь нет никаких требований к пространству, поэтому он может быть лишним. Я давно не занимался MI в C ++, но разве невиртуальное наследование не усложнит апкастинг?
Ури
Почему, ну почему кто-то хочет сделать dtor в этом случае чисто виртуальным? Какую выгоду это принесет? Вы просто навязали бы что-то на производные классы, которые они, вероятно, не должны включать - dtor.
Иоганн Герелл
2
Если есть ситуация, когда объект будет уничтожен через указатель на интерфейс, вы должны убедиться, что деструктор является виртуальным ...
Uri
В чистом виртуальном деструкторе нет ничего плохого. Это не обязательно, но в этом нет ничего плохого. Реализация деструктора в производном классе вряд ли является большой нагрузкой для разработчика этого класса. Посмотрите мой ответ ниже, чтобы узнать, почему вы это делаете.
Rodyland
+1 для виртуального наследования, потому что с интерфейсами более вероятно, что класс получит интерфейс из двух или более путей. Я выбираю защищенные деструкторы в интерфейсах, хотя.
документ
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
А также эта статья "Виртуальность" Херба Саттера.
user2067021
1

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

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

HIMS
источник
5
Это не то, что считается интерфейсом! Это просто абстрактный базовый класс с одним методом, который необходимо переопределить! Интерфейсы обычно представляют собой объекты, которые содержат только определения методов - «контракт», который должны выполнять другие классы при реализации интерфейса.
гитара