Термін "інтерфейс" в C ++


11

Java чітко розрізняє classта interface. (Я вважаю, що C # так само є, але я з цим не маю досвіду). Однак при написанні C ++ немає різниці між класом та інтерфейсом, що застосовується мовою.

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

Я завжди схилявся до підходу "писати речі найбільш очевидним чином", тому якщо в C ++ я отримав те, що можна назвати інтерфейсом на Java, наприклад:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() = 0;
};

і тоді я вирішив, що більшість реалізаторів Fooхотів би поділитися якоюсь загальною функціональністю, напевно, я напишу:

class Foo {
public:
  virtual void doStuff() = 0;
  ~Foo() {}
protected:
  // If it needs this to do its thing:
  int internalHelperThing(int);
  // Or if it doesn't need the this pointer:
  static int someOtherHelper(int);
};

Це робить це вже не інтерфейсом у сенсі Java.

Натомість C ++ має два важливі поняття, пов'язані з тією самою основною проблемою успадкування:

  1. virtual успадкування
  2. Класи без змінних членів не можуть займати зайвого місця при використанні в якості основи

    "Суб'єкти базового класу можуть мати нульовий розмір"

    Довідково

З тих, кого я намагаюся уникати №1, де це можливо - рідко трапляється сценарій, коли це справді є "найчистішим" дизайном. # 2, однак, є тонкою, але важливою відмінністю між моїм розумінням терміна "інтерфейс" та мовними особливостями мови C ++. В результаті цього я (майже) ніколи не називаю речі як "інтерфейси" в C ++ і розмовляю з точки зору базових класів та їх розмірів. Я б сказав, що в контексті C ++ "інтерфейс" є неправильним.

Мені подобається, що не так багато людей роблять таке розмежування.

  1. Чи можу я щось втрачати, дозволяючи (наприклад protected) нефункції virtualіснувати в "інтерфейсі" в C ++? (Моє відчуття якраз протилежне - більш природне місце для спільного коду)
  2. Чи має значення термін "інтерфейс" в C ++ - це означає лише чисте virtualчи було б справедливо називати класи C ++ без змінних членів інтерфейсом?

C # має той же множинне спадкування інтерфейсів, одиночне спадкоємство реалізації , як Java, але за рахунок використання загальних методів розширення , то internalHelperThingможна змоделювати практично у всіх випадках.
Sjoerd

Зауважте, що це погана практика виставляти віртуальних членів.
Клайм

публіка ~Foo() {}в абстрактному класі - це помилка за (майже) кожною обставиною.
Вовк

Відповіді:


11

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

Що стосується вашого прикладу: у Java (та подібному у C #) ваш код, ймовірно, означатиме розділення проблем:

interface IFoo {/*  ... */} // here is your interface

class FooBase implements IFoo 
{
     // make default implementations for interface methods
}

class Foo extends FooBase
{
}

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


3
Інше можливе значення "інтерфейсу" для програміста на C ++ - це publicчастини .hфайлу.
Девід Торнлі

Якою мовою є приклад коду? Якщо це спроба бути C ++, я збираюся плакати ...
Qix - МОНІКА ПОМИЛИ

@Qix: легко, прочитайте мою публікацію ще раз (там чітко зазначено, що код є на Java), і якщо ви отримали> 2000 балів, ви можете відредагувати мою публікацію, щоб додати еквівалентний код C ++, щоб зробити мій приклад більше ясний.
Док Браун

Якщо це Java, то це все ще неправильно, і ні, я не можу це редагувати; недостатньо символів для зміни. Java не використовує колонки для впровадження / розширення, тому мені було цікаво, чи це була спроба C ++ ...
Qix - MONICA ПОМИЛИЛИ

@Qix: якщо ви знайдете більше синтаксичних питань, можете зберегти їх як різдвяний подарунок :-)
Doc Brown

7

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

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

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

Чи можу я щось втрачати, дозволяючи (наприклад, захищеним) невіртуальним функціям існувати в "інтерфейсі" в C ++? (Моє відчуття якраз протилежне - більш природне місце для спільного коду)

Чистий інтерфейс повинен бути абсолютно абстрактним, оскільки він дозволяє визначити договір сумісності між класами, який, можливо, не обов'язково успадкував їх поведінку від загального предка. Коли буде реалізовано, ви хочете зробити вибір щодо того, дозволити поширювати поведінку реалізації в підкласі. Якщо ваших методів немає virtual, ви втрачаєте можливість продовжити цю поведінку пізніше, якщо вирішите, що вам потрібно створити класи нащадків. Незалежно від того, реалізація віртуальна чи ні, Інтерфейс визначає поведінкову сумісність сам по собі, тоді як клас забезпечує реалізацію такої поведінки для тих випадків, які представляє клас.

Чи має значення термін "інтерфейс" в C ++ - це означає лише чистий віртуальний або було б справедливо називати класи C ++ без змінних членів інтерфейсом?

Пробачте, але минуло давно, коли я справді написав серйозну заяву на C ++. Я пам'ятаю, що інтерфейс - це ключове слово для цілей абстракції, як я їх описав тут. Я б не називав C ++ будь-яких класів інтерфейсом, я б сказав, що клас має інтерфейс у межах значень, які я окреслив вище. У цьому сенсі термін є значущим, але це дійсно залежить від контексту.


1
+1 для розрізнення інтерфейсу "мати" та "бути".
Док Браун

6

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

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

Уникайте податків на спадщину: Спадщина - це друге найкрупніше взаємозв'язок у С ++, поступаючись лише дружбі. Щільне зчеплення небажане і слід уникати, де це можливо. Тому віддайте перевагу композиції перед спадкуванням, якщо ви не знаєте, що останній справді приносить користь вашому дизайну.

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

STL є досить хорошим прикладом цього. Якнайбільше допоміжних функцій витягується <algorithm>замість того, щоб бути у контейнерних класах. Наприклад, оскільки sort()використовує API громадських контейнерів для своєї роботи, ви знаєте, що можете реалізувати власний алгоритм сортування без зміни коду STL. Ця конструкція дозволяє бібліотекам на зразок boost, які покращують STL замість заміни, STL не потребує нічого знати про прискорення.


2

Чи можу я щось втрачати, дозволяючи (наприклад, захищеним) невіртуальним функціям існувати в "інтерфейсі" в C ++? (Моє відчуття якраз протилежне - більш природне місце для спільного коду)

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

Це один із тих випадків, коли не існує відповіді, що відповідає одному розміру, і вам потрібно використовувати свій досвід та судження для прийняття рішення. Якщо ви можете зі 100% впевненістю сказати, що кожен можливий підклас вашого інакше повністю віртуального класу Fooзавжди потребуватиме реалізований захищений метод bar(), тоді Fooце правильне місце для нього. Після того, як у вас є підклас Baz, який не потребує bar(), вам або доведеться жити з тим фактом, що Bazвін матиме доступ до коду, якого він не повинен, або пройти процедуру перестановки ієрархії вашого класу. Перший не є хорошою практикою, а останній може зайняти більше часу, ніж кілька хвилин, які знадобляться, щоб впорядкувати речі належним чином.

Чи має значення термін "інтерфейс" в C ++ - це означає лише чистий віртуальний або було б справедливо називати класи C ++ без змінних членів інтерфейсом?

У розділі 10.4 стандарту C ++ згадується використання абстрактних класів для реалізації інтерфейсів, але формально їх не визначається. Термін має сенс у загальному контексті інформатики, і кожен компетентний повинен розуміти, що " Fooінтерфейс для (будь-якого)" передбачає певну форму абстракції. Тим, хто стикається з мовами з визначеними конструкціями інтерфейсу, може здатися, що це віртуально, але кожен, з ким потрібно реально працювати Foo, вивчить його визначення, перш ніж продовжувати.


2
Яка різниця між наданням чистої віртуальної декларації та декларацією плюс реалізацією? В обох випадках має бути реалізація, і bazзавжди може бути така реалізація, як return false;і будь-яка інша. Якщо метод не застосовується до всіх підкласів, він не належить до базового абстрактного класу в будь-якій формі.
Девід Торнлі

1
Я думаю, що ваше останнє речення говорить, що ми знаходимося на одній сторінці: немає жодної причини, щоб у вас не було захищених методів в абстрактних класах, але вони не повинні бути вище в дереві спадкування, ніж абсолютно необхідні. Я просто думаю, що якщо клас повинен піддавати виконання, це не в потрібному місці на дереві.
Blrfl
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.