Дизайн класса против IDE: действительно ли функции, не являющиеся членами группы, действительно стоят того? - PullRequest
4 голосов
/ 25 сентября 2008

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

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

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

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

Так что мне интересно, действительно ли оно того стоит. Как вы справляетесь с этим?

Ответы [ 7 ]

5 голосов
/ 26 сентября 2008

Скотт Мейерс схож с мнением Саттера, см. здесь .

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

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

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

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

5 голосов
/ 26 сентября 2008

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

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

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

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

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

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

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

2 голосов
/ 26 сентября 2008

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

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

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

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

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

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

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

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

Сохраняет небольшой класс, что, в свою очередь, облегчает тестирование и обслуживание.

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

1 голос
/ 26 сентября 2008

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

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

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

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

0 голосов
/ 26 сентября 2008

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

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

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

0 голосов
/ 26 сентября 2008

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

0 голосов
/ 26 сентября 2008

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

::prefix

или

namespace::prefix
...