Как стать автором
Обновить

Комментарии 35

Может кто-то объяснить простыми словами не программисту — зачем нужны интерфейсы?
Это как список ToDo? Просто для удобства? Программист-руководитель написал интерфейс, другой программист им пользуется пока третий пишет реализацию? В чем "реализуется истинная инкапсуляция, невозможная при использовании обычного класса"? Это без относительно С++, в Java я так понимаю они тоже есть.

Как там в простых примерах — есть у меня интерфейс Animal — в нем виртуальные функции Jump, Run, Eat — которые где то реализованы в производном классе. Зачем этот промежуточный этап в виде интерфейса?

Интерфейс позволяет зависеть от абстракции, а не от реализации.


Лучше пример с логированием подойдёт, например


У Вас есть интерфейс ILogger с методом Log(message: string)


Всё кто используют этот метод не знают конкретной реализации, может логгер пишет в БД, а может в файл. А может вообще отправляет на почту админу.
При зависимости от интерфейса Вы легко можете поменять реализацию, не трогая потребителей интерфейса

вот поставляете вы библиотеку. Без интерфейсов, вы раскрываете часть деталей реализации классов — protected/private методы и т.д. Есть вероятность такого использования класса юзером, что изменения реализации в библиотеке сломают бинарную совместимость с пользовательским кодом (банально поменялся sizeof класса-реализации). Либо же вам придется прятать их за другими слоями абстракций, например PIMPL.

У интерфейсов есть и другие достоинства. Один класс может реализовывать несколько разных интерфейсов. Интерфейсы хорошо версионируются:
class IFoo_v1
{
public:
    virtual void bar() = 0;
};

class IFoo_v2 : public IFoo_v1
{
public:
    virtual void baz() = 0; // Метод, появившийся только во второй версии
    [[deprecated]] void bar() override = 0; // Устаревший во второй версии метод
};

using IFoo = IFoo_v2; // Алиас на последнюю версию. Пользователи могут использовать его или конкретную

// implementation
class Foo : public IFoo
{
//...
};
Интерфейсы используются во многих языках программирования, и материалов по поводу того, зачем они нужны, тоже очень много. Но при их проектировании и реализации в С++ имеется много проблем, тонкостей, вариантов, связанных с особенностями языка С++. Разбору всего этого и посвящена статья.
Представьте себе геймпад. Вы тыкаете кнопки, а на другом конце провода персонаж в игре что-то делает. А как это реализовано? Есть системный API (читайте интерфейс) и игроделам не надо разрабатывать управление под каждый геймпад отдельно — им важно лишь, чтобы геймпад дергал это API, тогда все будет работать как задумано. Так же и производителям геймпадов не надо рассылать свои спецификации и софт куче игровых компаний, чтобы те реализовали поддержку их геймпада. Достаточно реализовать API и готово — ваше устройство работает со всеми этими играми. В итоге обе стороны дергают абстракцию и от конкретной реализации не зависят.

К уже написанному добавлю, что интерфейсы и реализации подчиняются принципу подстановки Лисков. Это значит, что везде, где ожидается некий базовый класс, можно передать объект любого дочернего класса, и всё должно работать верно. Интерфейсы и есть такие базовые классы, а их реализации — дочерние, поэтому если функция ожидает аргумент класса интерфейса, в неё можно передать любой дочерний класс-реализацию. Если бы функция ожидала конкретный класс-реализацию, в неё нельзя было бы передать другую реализацию, т.к. она является не дочерней, а сиблингом (т.е. братом-сестрой, по аналогии с родством среди людей). Таким образом, гибкость архитектуры становится сильно хуже, и программа теряет расширяемость и модифицируемость.

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

Интерфейс описывает требования, чтобы объектом можно было пользоваться. Причём тому, кто им пользуется совсем не важно знать, как оно работает.

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

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

Спасибо за комментарий, очень образное описание сути интерфейса. Многие языки программирования поддерживают интерфейсы на уровне языка (например C#, Java), но вот C++ нет. Приходится эмулировать интерфейсы с помощью чисто виртуальных функций, но при этом возникают достаточно много тонкостей и потенциальных проблем. В своей статье я как раз и попытался разобраться с этими проблемами.

Мне кажется, что «умный указатель» предпочтительнее. Вполне устоявшийся термин.
Мне кажется, «умный» можно отнести к устаревшим терминам. В [Josuttis], [Meyers3] и ряде других последних книг, посвященных современному C++, используется термин «ителлектуальный».
Заглянем в «Effective Modern C++ 42 SPECIFIC WAYS TO IMPROVE YOUR USE OF C++11 AND C++14» ISBN: 978-1-491-90399-5 товарища Меерса:

Chapter 4: Smart Pointers
Smart pointers are one way to address these issues. Smart pointers are wrappers
around raw pointers that act much like the raw pointers they wrap, but that avoid
many of their pitfalls. You should therefore prefer smart pointers to raw pointers.
Smart pointers can do virtually everything raw pointers can, but with far fewer
opportunities for error.

Мне кажется, что термин умные указатели никуда не делся. Как они были smart pointer так и остались.
Ну а теперь загляните в русский перевод этой книги. Там используется термин «интеллектуальный указатель». И в последнее время так переводят другие переводчики. На английском это всегда было smart pointer. При выборе перевода я, по возможности, стараюсь использовать широко известные публикации на русском.
Если smart pointer во всем мире как был smart poniter-ом так им и остался даже не думая перерождаться в intellectual — возможно, это лишь дилетантский произвол переводчика/корректора в указанных издательствах?
Ну на счет «дилетантский» я бы категорически возразил. Мейерса переводил и редактировал И.В.Красиков, авторитетнейший переводчик, он переводит и редактирует практически все книги по тематике C++ в издательстве «Вильямс» (Герб Саттер, Андрей Александреску, Николаи Джосаттис). Его переводы стали стандартом де-факто. В технической терминологии достаточно много тонкостей и не всегда первый попавшийся вариант перевода будет лучше.
авторитет одного переводчика — ничто по сравнению с общепринятой в мире терминологией, как бы сильно он не любил пафосные слова
Ну, наверное, стоит уточнить: в русскоязычном мире, обсуждается перевод на русский язык. И как показывает опыт, словом «общепринятый» (тоже, кстати, пафосное слово) очень часто прикрываются личные вкусы и пристрастия автора.
так тут нет автора с пристрастиями. Приняли в международном сообществе термин «smart pointer» (его происхождение нас не волнует), берется буквальный перевод «умный указатель» и становится общепринятым в русскоязычном сообществе. Другое дело если бы буквальный перевод был плохим
Сколько себя помню, они были умными. Указатели уже поумнели на столько, что стали проявлять собственный интеллект и их теперь переименовывать необходимо?

Похоже на студенческую курсовую работу. Какая здесь новая информация?

Просто удобная выжимка для общего ознакомления
Написать что-то принципиально новое на тему программирования довольно сложно. Моя цель была собрать и изложить все варианты, связанные с интерфейсными классами, так, что бы проектировщик мог сделать осознанный выбор. Моя практика показывает, что программисты очень редко правильно реализуют интерфейсные классы (хотя делают это достаточно часто), то есть для большинства данная статья содержит новый, ранее не знакомый материал и будет весьма полезна.
> COM-интерфейсы являются примером интерфейсных классов
Очень хотелось бы увидеть примеры «комфортной» реализации COM интерфейсов (interface+dispinterface) на C++.
Для реализации COM на С++ существует специальная библиотека — ATL от Microsoft (входит в состав Visual Studio). По ней есть книги. Раньше у меня была отличная книга, где демонстрировалось создание COM объектов на C++ «руками», без всяких библиотек, но кто-то зачитал. Ну, вообще, COM уже не модная технология.
Дело не в моде. Просто на C++ с этим COM и ATL грсуть и печаль почучается (книги). Хочется увидеть как в современном C++17,20… справляются с такой старой задачей.
Дело как раз в моде. Заинтересованное лицо — Microsoft — продвигает инфраструктуру .NET вместо COM, потому и не развивает COM. Авторы стандартов C++17,20 вообще не думают о COM, потому для COM ничего не меняется.

С другой стороны, в Delphi, VB, и скриптовых языках от MS (jscript, vbs) за счёт IDispatch уже довольно удобно пользоваться COM, и потому никаких изменений не нужно.
Тут ничего не поделать, COM медленная (в смысле разработки) и печальная технология, C++17 уж точно не планировался для нее. Раньше COM объекты можно было писать на Visual Basic 6, Delphi. До сих пор можно писать на Qt. Это, конечно, побыстрее.
Не увидел Non-Virtual Interface (Не виртуальный интерфейс).
Удобная вещь когда нужно подготовить «контекст» перед вызовом виртуальной ф-ции
NVI это другая модель использования виртуальных, разновидность Шаблонного Метода Банды Четырех. Интерфейсные классы носят более технологический характер, с их помощью реализуется доступ к непрозрачному модулю. В C++ нет понятия интерфейс и его приходится моделировать с помощью виртуальных функций.

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


Следует с осторожностью объявлять функции-члены интерфейсных классов как const.

Вы могли бы раскрыть здесь подробнее, какие сложности нас могут ожидать на этом пути.

Иногда объявление интерфейса идет рука об руку с его реализацией, это делает одна команда. Тут проблем не может быть. Но, например, для plugin'а объявлять и использовать интерфейс может одна команда, а реализовывать интерфейс совершенно другая команда, у которой свой контекст при реализации интерфейса и он может не очень хорошо стыковаться с константностью. Например, доступ к свойству может быть реализован с использованием lazy evaluation.
Например, доступ к свойству может быть реализован с использованием lazy evaluation.

вы неправильно понимаете константность. Это всё-таки про неизменяемость наблюдаемого состояния объекта, а не внутреннего. Яркий пример — COW структуры данных, thread-safe классы с mutex'ами и пр. И если ваш getter реализован через lazy evaluation, но он никак не может поменять наблюдаемое состояние класса, его наоборот будет правильно пометить как const
Ну может пример не совсем удачный. Но суть в том, что разработчики и пользователи интерфейса не всегда могут знать контекст реализации и константность может добавить дополнительные проблемы при реализации интерфейса, например вынудит использовать mutable. Вообще я планирую написать статью про константность в C++.
Если пользователям интерфейса пришлось узнавать о реализации, то что-то пошло не так…
А константность, на мой взгляд, нужна для облегчения использования интерфейса, а не наоборот. Она только подчёркивает свойства метода, позволяет избежать ошибок ещё во время компиляции.
Злоупотреблять, расставляя const где попало, конечно не стоит.
Использование mutable не является проблемой. Это лишь способ указать что поле не влияет на наблюдаемое состояние класса.
Моё мнение таково, что константность должна нести смысловую нагрузку. «Не делать методы константными потому что из-за этого кому-то может быть придется делать mutable поле в реализации» — откровенно неправильный подход к архитектуре.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории