Когда я должен отдавать предпочтение функциям, не являющимся членами, а не функциям-членам? - PullRequest
16 голосов
/ 19 октября 2011

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

Пример:

// Web browser allows to clear something
class WebBrowser {
public:
  ...
  void clearCache();
  void clearHistory();
  void removeCookies();
  ...
};

Многие пользователи захотятвыполнить все эти действия вместе, поэтому WebBrowser может также предложить функцию, которая делает именно это:

class WebBrowser {
public:
  ...
  void clearEverything();  // calls clearCache, clearHistory, removeCookies
  ...
};

Другой способ - определить функцию, не являющуюся членом, не являющейся другом.

void clearBrowser(WebBrowser& wb)
{
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

Функция, не являющаяся членом, лучше, потому что «она не увеличивает количество функций, которые могут получить доступ к закрытым частям класса.», Что приводит к лучшей инкапсуляции.

Функции, подобные clearBrowser удобные функции , потому что они не могут предложить никакой функциональности, которую WebBrowser клиент уже не мог получить другим способом.Например, если clearBrowser не существует, клиенты могут просто вызвать clearCache, clearHistory и removeCookies.

Для меня пример вспомогательных функций разумноНо есть ли какой-нибудь пример, кроме удобной функции, когда версия, не являющаяся членом, превосходит?

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

Ответы [ 4 ]

20 голосов
/ 19 октября 2011

В общем, Каковы правила использования каких?

Вот правила Скотта Мейера ( source ):

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

if (f needs to be virtual)
    make f a member function of C;
else if (f is operator>> or operator<<)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f needs type conversions on its left-most argument)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f can be implemented via C's public interface)
   make f a non-member function;
else
   make f a member function of C;

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

Что в значительной степени подводит итог всего этого, и, на мой взгляд, вполне разумно.

3 голосов
/ 19 октября 2011

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

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

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

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

M.

0 голосов
/ 19 октября 2011

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

Если WebBrowser повторно используется во многих местах, зависимости / клиенты могут определять несколько вспомогательных функций. Это делает ваши классы (WebBrowser) легкими и простыми в управлении.

Обратным будет то, что WebBrowser в конечном итоге доставит удовольствие всем клиентам и станет просто монолитным зверем, которого трудно изменить.

Считаете ли вы, что классу не хватает функциональности после его использования в нескольких сценариях? Появляются ли шаблоны в ваших удобных функциях? Лучше (IMO) отложить формальное расширение интерфейса класса до появления шаблонов, и есть веская причина для добавления этой функциональности. Минимальный класс легче поддерживать, но вы не хотите, чтобы избыточные реализации были повсюду, потому что это ложится бременем на обслуживание ваших клиентов.

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

Также будут случаи, когда вы понимаете, что что-то действительно отсутствует в WebBrowser при использовании.

0 голосов
/ 19 октября 2011

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

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

Это означает, что хотя clearEverything можно (и, честно говоря, вполне вероятно) сделать членом, мы не делаем его однимпотому что это технически не должно быть.Это дает вам две вещи:

  1. Вам не нужно брать на себя ответственность за наличие метода clearEverything в вашем общедоступном интерфейсе (после того, как вы отправите его с одним, вы поженились на нем на всю жизнь).
  2. Количество функций с доступом к private членам класса на единицу меньше, поэтому любые изменения в будущем будут проще выполнять и с меньшей вероятностью будут вызывать ошибки.

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

...