Ява проводит четкое различие между 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 ++ есть две важные концепции, связанные с одной и той же проблемой наследования:
virtual
inhertianceКлассы без переменных-членов не могут занимать дополнительное место при использовании в качестве базы
«Подобъекты базового класса могут иметь нулевой размер»
Из тех, кого я стараюсь избегать №1, везде, где это возможно - редко можно встретить сценарий, где это действительно самый «чистый» дизайн. # 2, однако, является тонким, но важным различием между моим пониманием термина «интерфейс» и особенностями языка C ++. В результате этого я в настоящее время (почти) никогда не называю вещи «интерфейсами» в C ++ и говорю о базовых классах и их размерах. Я бы сказал, что в контексте C ++ «интерфейс» является неправильным.
Хотя до меня дошло, что не так много людей делают такое различие.
- Стоит ли мне что-то терять, позволяя (например
protected
) не-virtual
функциям существовать в «интерфейсе» в C ++? (Мое чувство как раз наоборот - более естественное место для общего кода) - Является ли термин «интерфейс» значимым в C ++ - подразумевает ли он только чистый
virtual
или было бы справедливо называть классы C ++ без переменных-членов интерфейсом по-прежнему?
источник
internalHelperThing
можно смоделировать почти во всех случаях.~Foo() {}
в абстрактном классе является ошибкой (почти) при любых обстоятельствах.Ответы:
В C ++ термин «интерфейс» имеет не только одно общепринятое определение - поэтому всякий раз, когда вы собираетесь его использовать, вы должны сказать, что именно вы имеете в виду - виртуальный базовый класс с реализациями по умолчанию или без них, файл заголовка, открытые члены. произвольного класса и так далее.
Что касается вашего примера: в Java (и аналогично в C #) ваш код, вероятно, будет подразумевать разделение задач:
В C ++ вы можете сделать это, но вы не обязаны это делать. И если вам нравится называть класс интерфейсом, если он не имеет переменных-членов, но содержит реализации по умолчанию для некоторых методов, просто сделайте это, но убедитесь, что все, с кем вы разговариваете, знают, что вы имеете в виду.
источник
public
части.h
файла.Это звучит немного похоже на то, что вы, возможно, попали в ловушку путаницы смысла того, что интерфейс является концептуально как реализацией (интерфейс - строчная буква «i»), так и абстракцией (интерфейс - прописная буква «I»). ).
Что касается ваших примеров, ваш первый кусочек кода - просто класс. В то время как у вашего класса есть интерфейс в том смысле, что он предоставляет методы для обеспечения доступа к его поведению, он не является интерфейсом в смысле объявления интерфейса, предоставляющего уровень абстракции, представляющий тип поведения, которому вы, возможно, захотите, чтобы классы осуществлять. Ответ Дока Брауна на ваш пост показывает вам, о чем я здесь говорю.
Интерфейсы часто рекламируются как «обходной путь» для языков, которые не поддерживают множественное наследование, но я чувствую, что это скорее заблуждение, чем жесткая истина (и я подозреваю, что я могу быть огорчен этим утверждением!). Интерфейсы действительно не имеют ничего общего с множественным наследованием, так как они не требуют наследования или предков для обеспечения функциональной совместимости между классами или абстракции реализации для классов. На самом деле, они могут позволить вам эффективно покончить с наследованием полностью, если вы захотите реализовать свой код таким образом - не то, чтобы я полностью рекомендовал его, но я просто говорю, что вы могли бысделай это. Таким образом, реальность такова, что независимо от вопросов наследования интерфейсы предоставляют средства, с помощью которых можно определять типы классов, устанавливая правила, определяющие, как объекты могут взаимодействовать друг с другом, что позволяет вам определять поведение, которое ваши классы должны поддерживать без диктовать конкретный метод, используемый для реализации этого поведения.
Чистый Интерфейс должен быть полностью абстрактным, поскольку он позволяет определить контракт совместимости между классами, которые не обязательно унаследовали свое поведение от общего предка. При реализации вы хотите сделать выбор о том, разрешить ли расширение реализации в подклассе. Если ваши методы не таковы
virtual
, вы потеряете возможность расширять это поведение позже, если решите, что вам нужно создать классы-потомки. Независимо от того, является ли реализация виртуальной или нет, Интерфейс определяет поведенческую совместимость сам по себе, в то время как класс обеспечивает реализацию этого поведения для экземпляров, которые представляет класс.Простите, но я давно писал серьезное приложение на C ++. Напомню, что интерфейс - это ключевое слово для целей абстракции, как я их здесь описал. Я бы не назвал классы C ++ любого рода интерфейсом, я бы сказал, что у класса есть интерфейс в значениях, которые я обрисовал выше. В этом смысле термин имеет смысл, но это действительно зависит от контекста.
источник
Java-интерфейсы не являются «обходным путем», они представляют собой обдуманное проектное решение, позволяющее избежать некоторых проблем с множественным наследованием, таких как наследование алмазов, и поощрять методы проектирования, минимизирующие связывание.
Ваш интерфейс с защищенными методами - это учебник, в котором нужно «предпочесть композицию наследованию». Чтобы процитировать Саттера и Александреску в разделе под этим именем из их превосходных стандартов кодирования C ++ :
Включив в свой интерфейс вспомогательные функции, вы можете сэкономить немного времени на наборе текста, но вы вводите связь, которая повредит вам в будущем. Почти всегда лучше в долгосрочной перспективе разделить вспомогательные функции и передать их в
Foo*
.STL является довольно хорошим примером этого. Как можно больше вспомогательных функций извлекаются
<algorithm>
вместо того, чтобы находиться в классах контейнера. Например, посколькуsort()
для своей работы используется API открытого контейнера, вы знаете, что можете реализовать собственный алгоритм сортировки без необходимости изменения кода STL. Такая конструкция позволяет использовать такие библиотеки, как boost, которые улучшают STL вместо его замены, и STL не нужно ничего знать о boost.источник
Вы могли бы подумать об этом, но размещение защищенного, не виртуального метода в другом абстрактном классе диктует реализацию любому, кто пишет подкласс. Это наносит ущерб цели интерфейса в его чистом смысле, то есть обеспечивает облицовку, которая скрывает то, что находится под ним.
Это один из тех случаев, когда нет универсального ответа, и вы должны использовать свой опыт и суждения для принятия решения. Если вы можете со 100% уверенностью сказать, что каждый возможный подкласс вашего полностью виртуального класса
Foo
всегда будет нуждаться в реализации защищенного методаbar()
, тогдаFoo
это подходящее место для него. Если у вас есть подклассBaz
, который вам не нуженbar()
, выBaz
должны либо согласиться с тем фактом, что у него будет доступ к коду, которого он не должен, либо выполнить перестройку иерархии классов. Первое не является хорошей практикой, а второе может занять больше времени, чем те несколько минут, которые потребовались бы для того, чтобы правильно все наладить.Раздел 10.4 стандарта C ++ упоминает об использовании абстрактных классов для реализации интерфейсов, но не определяет их формально. Этот термин имеет смысл в общем контексте информатики, и любой компетентный должен понимать, что «
Foo
интерфейс для (чего бы то ни было)» подразумевает некоторую форму абстракции. Те, кто знаком с языками с определенными интерфейсными конструкциями, могут думать, что они чисто виртуальные, но любой, кому действительно нужно работатьFoo
, посмотрит на его определение, прежде чем продолжить.источник
baz
всегда может быть реализация, подобнаяreturn false;
или любая другая. Если метод не применяется ко всем подклассам, он не принадлежит базовому абстрактному классу ни в какой форме.