Классный дизайн против.IDE:Действительно ли стоит использовать функции, не являющиеся членами и не друзьями?

StackOverflow https://stackoverflow.com/questions/135634

Вопрос

В (в остальном) превосходной книге Стандарты кодирования C++, пункт 44, озаглавленный «Предпочитаю писать функции, не являющиеся членами и не дружественными», Саттер и Александреску рекомендуют, чтобы членами этого класса были только функции, которым действительно необходим доступ к членам класса.Все другие операции, которые можно записать с использованием только функций-членов, не должны быть частью класса.Они должны быть не членами и не друзьями.Аргументы таковы:

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

Хотя я вижу ценность этих аргументов, я вижу огромный недостаток: моя IDE не может помочь мне найти эти функции! Всякий раз, когда у меня есть какой-то объект и я хочу посмотреть, какие операции над ним доступны, я не могу просто набрать "pMysteriousObject->" и больше не получим список функций-членов.

Поддержание чистоты дизайна в конечном итоге облегчит вам жизнь программиста.Но на самом деле это сделало бы мою жизнь намного сложнее.

Вот мне и интересно, стоит ли вообще заморачиваться. Как ты с этим справляешься?

Это было полезно?

Решение

В этом вопросе мне придется не согласиться с Саттером и Александреску.Я думаю, что если поведение функции foo() попадает в рамки класса Barобязанности, то foo() должен быть частью bar().

Дело в том, что foo() не нуждается в прямом доступе к Barданные-члены не означают, что они концептуально не являются частью Bar.Это также может означать, что код хорошо факторизован.Нередко есть функции-члены, которые выполняют все свое поведение через другие функции-члены, и я не понимаю, почему так должно быть.

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

Что касается этих конкретных моментов:

Это способствует инкапсуляции, поскольку меньше кода требует доступа к внутренним компонентам класса.

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

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

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

Это позволяет сохранить класс небольшим, что, в свою очередь, упрощает тестирование и поддержку.

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

Другие советы

Скотт Мейерс придерживается того же мнения, что и Саттер, см. здесь.

Он также ясно заявляет следующее:

«Основываясь на своей работе с различными строкоподобными классами, Джек Ривз заметил, что некоторые функции просто «не кажутся» правильными, когда их делают нечленами, даже если они могут быть не-членами.«Лучший» интерфейс для класса можно найти, только сбалансировав множество конкурирующих проблем, среди которых степень инкапсуляции — лишь одна из них».

Если функция будет чем-то, что «просто имеет смысл» быть функцией-членом, сделайте ее таковой.Аналогично, если это на самом деле не является частью основного интерфейса и «просто имеет смысл» не быть участником, сделайте это.

Следует отметить, что в перегруженных версиях, например, оператора ==(), синтаксис остается прежним.Так что в этом случае у вас нет причин нет сделать его плавающей функцией, не являющейся членом и не являющейся другом, объявленной в том же месте, что и класс, если только ему действительно не нужен доступ к частным членам (по моему опыту, это случается редко).И даже в этом случае вы можете определить оператор!=() как нечлен и в терминах оператора==().

Я не думаю, что было бы неправильно сказать, что Саттер, Александреску и Мейерс сделали для качества C++ больше, чем кто-либо другой.

Они задают один простой вопрос:

Если функция полезности имеет два независимых класса в качестве параметров, какой класс должен «владеть» функцией-членом?

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

В обоих этих примерах ваша IDE предоставит неполную информацию, и вам придется использовать «старомодный способ».

Учитывая, что самые влиятельные эксперты C++ в мире считают, что функции, не являющиеся членами, с параметром класса являются частью интерфейса классов, это скорее проблема вашей IDE, а не стиля кодирования.

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

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

На самом деле я согласен, что обеспечить соблюдение этого требования непросто.Зачастую проще просто добавить в класс еще один метод, особенно если вы используете IDE, которая действительно может просто добавить новый метод в существующий класс.

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

Пример:создайте класс Point, затем добавьте класс PointDrawer, чтобы нарисовать его в растровом изображении, PointSerializer, чтобы сохранить его и т. д.

Если вы дадите им общий префикс, то, возможно, ваша IDE поможет, если вы наберете

::prefix

или

namespace::prefix

Во многих языках ООП недружественные неклассовые методы — это граждане третьего сорта, которые живут в приюте, ни с чем не связанные.Когда я пишу метод, мне нравится выбирать хороших родителей — подходящий класс, — где у них больше всего шансов почувствовать себя желанными гостями и получить помощь.

Я бы подумал, что IDE действительно вам помогает.

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

Если бы вы находились в пределах класса и набрали это-> тогда защищенные функции будут отображаться в списке.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top