Подтвердить что ты не робот

Эффективный С++ Элемент 23 Предпочитайте функции, отличные от членов, не являющихся членами для функций-членов

В то время, когда вы сталкиваетесь с некоторыми фактами о дизайне классов, в частности, должны ли они быть членами или нет, я посмотрел в "Эффективный С++" и нашел пункт 23, а именно "Предпочесть не-членские функции, отличные от друга" для функций-членов. Чтение того, что из первых рук в примере с веб-браузером имело какой-то смысл, однако удобные функции (названные такими нечеткими функциями, как это в книге) в этом примере изменят состояние класса, не так ли?

  • Итак, первый вопрос, не должны ли они быть членами, чем?

  • Читая немного дальше, он считает, что функции STL и действительно некоторые функции, которые не реализованы некоторыми классами, реализованы в stl. Следуя идеям книги, они развиваются в некоторые удобные функции, которые упаковываются в некоторые разумные пространства имен, такие как std::sort, std::copy from algorithm. Например, класс vector не имеет функции sort, и используется функция stl sort, поэтому она не является членом векторного класса. Но можно было бы также разделить те же рассуждения на некоторые другие функции в векторном классе, такие как assign, что также не может быть реализовано как член, а как функция удобства. Однако это также изменяет внутреннее состояние объекта, например, на котором он работал. Итак, в чем причина этого тонкого, но важного (я думаю) вопроса.

Если у вас есть доступ к книге, вы можете уточнить эти моменты немного больше для меня?

4b9b3361

Ответ 1

Доступ к книге ни в коем случае не требуется.

Вопросы, которые мы имеем здесь, - это зависимость и повторное использование.

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

В хорошо разработанном программном обеспечении вы применяете принцип DRY (Do not Repeat Yourself), потому что, когда требуется изменение, это болезненно и подвержено ошибкам, чтобы повторить его в десятке разных мест.

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

В С++ STL (не вся стандартная библиотека) была разработана с явными целями:

  • зависимостей резания
  • возможность повторного использования

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

Например, если вы думаете о требованиях алгоритма sort. Для реализации, используемой (в общем) STL, она требует (из контейнера):

  • эффективный доступ к элементу по заданному индексу: произвольный доступ
  • возможность обмена двумя элементами: не ассоциативный

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

Каковы контейнеры в С++, которые удовлетворяют этому?

  • базовый C-массив
  • deque
  • vector

И любой контейнер, который вы можете написать, если вы обратите внимание на эти детали.

Было бы расточительно, не правда ли, переписать (скопировать/вставить/настроить) sort для каждого из них?

Обратите внимание, например, что существует метод std::list::sort. Зачем? Потому что std::list не предлагает произвольный доступ (неформально myList[4] не работает), поэтому sort из алгоритма не подходит.

Ответ 2

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

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

Часть дизайна интерфейса - это искусство поиска минимального набора функций-членов, так что все операции, которые вы, возможно, захотите выполнить на объекте, могут быть реализованы с достаточной эффективностью с точки зрения них. И этот набор не должен поддерживать операции, которые не должны выполняться в классе. Таким образом, вы не можете просто реализовать кучу функций getter и setter и называть это хорошо.

Ответ 3

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

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

Кстати, вы должны купить свою копию Effective С++, это отличная книга, но не старайтесь всегда соблюдать каждый пункт этой книги. Объектно-ориентированный дизайн и хорошие практики (из книг и т.д.) И опыт (я думаю, что он также написан в Effective С++ где-то).

Ответ 4

Итак, первый вопрос, не должны ли они быть членов, чем?

Нет, этого не происходит. В идиоматическом дизайне класса С++ (по крайней мере, в идиомах, используемых в Effective С++), функции non-friend, не связанные друг с другом, расширяют интерфейс класса. Они могут считаться частью публичного API для класса, несмотря на то, что они не нужны и не имеют частного доступа к классу. Если этот проект "не ООП" каким-то определением ООП, тогда, ОК, идиоматический C++ не является ООП этим определением.

растянуть те же рассуждения на некоторые другие функции в векторном классе

Правда, существуют некоторые функции-члены стандартных контейнеров, которые могли бы быть свободными функциями. Например, vector::push_back определяется в терминах insert и, безусловно, может быть реализован без личного доступа к классу. В этом случае push_back является частью абстрактного понятия BackInsertionSequence, которое реализует вектор. Такие обобщенные концепции пересекают дизайн конкретных классов, поэтому, если вы разрабатываете или реализуете свои собственные общие понятия, которые могут влиять на то, где вы добавляете функции.

Конечно, есть части стандарта, которые, возможно, были бы разными, например std::string имеет слишком много функций-членов. Но что сделано, и эти классы были разработаны до того, как люди действительно обосновались в том, что мы теперь можем назвать современным стилем С++. Класс работает в любом случае, поэтому существует только такая практическая выгода, от которой вы можете столкнуться, если будете беспокоиться о различии.

Ответ 5

Мотивация проста: поддерживайте согласованный синтаксис. Как класс эволюционирует или используется, различные функции, не являющиеся членами, будут появляются; вы не хотите изменять интерфейс класса, чтобы добавить что-то например, toUpper к классу строк. (В случае std::string, конечно, вы не можете.) Скотт беспокоится, что когда это происходит, вы оказываетесь в несогласованном синтаксисе:

s.insert( "abc" );
toUpper( s );

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

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

Ответ 6

Различные мысли:

  • Хорошо, когда не-члены работают через открытый API класса, так как он уменьшает количество кода, который:
    • необходимо тщательно отслеживать, чтобы гарантировать инварианты класса,
    • необходимо изменить, если реализация объекта была изменена.
  • Если это не достаточно хорошо, не-член все равно может быть сделан friend.
  • Написание функции, не являющейся членом, обычно является менее удобным, поскольку члены не подразумеваются в области видимости, но если вы рассматриваете эволюцию программы:
    • Как только существует функция, не являющаяся членом, и становится понятным, что одна и та же функциональность будет полезна для других типов, обычно легко преобразовать функцию в шаблон и иметь ее доступную не только для обоих типов, но и для произвольного будущего типы тоже. Другими словами, шаблоны без членства позволяют использовать еще более гибкое повторное использование алгоритмов, чем полиморфизм во время выполнения/виртуальная диспетчеризация: шаблоны позволяют что-то известное как утиная печать.
    • Существующий тип, поддерживающий полезную функцию-член , поощряет вырезание и вставку для других типов, которые хотели бы аналогичного поведения, потому что большинство способов преобразования функции для повторного использования требуют, чтобы каждый доступ к неявному члену быть сделанным явным доступом к определенному объекту, который будет более тедием 30+ секунд для программиста....
  • Функции-члены позволяют обозначать object.function(x, y, z), что ИМХО очень удобно, выразительно и интуитивно. Они также лучше работают с функциями обнаружения/завершения во многих IDE.
  • Разделение как функции члена и нечлена может помочь сообщать о сущностной природе класса, его инвариантах и ​​фундаментальных операциях и логически группировать дополнительные и, возможно, специальные функции "удобства". Подумайте о мудрости Тони Хоура: "Существует два способа построения дизайна программного обеспечения: один из способов сделать его настолько простым, что, очевидно, нет недостатков, а наоборот - сделать его настолько сложным, что есть нет очевидных недостатков. Первый метод намного сложнее".

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

(Всесторонний ответ - обязательное чтение, трижды, если оно вам ново.)

Ответ 7

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