Когда я предпочитаю функции участника, не являющиеся членами, для функций-членов?
Мейерс упомянул в своей книге "Эффективный 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
сами.
Для меня пример удобных функций является разумным. Но есть ли какой-либо пример, отличный от удобства, когда отличная от него версия?
В более общем плане каковы правила использования
Ответы
Ответ 1
В общем, каковы правила использования, которые?
Вот правила Скотта Мейера (источник):
У Скотта есть интересная статья в печати, которая защищает что функции, не являющиеся членами-не-друга, улучшают инкапсуляцию для классов. Он использует следующий алгоритм для определения где функция 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 public interface)
make f a non-member function;
else
make f a member function of C;
Его определение инкапсуляции включает число функций, на которые влияют частные данные члены изменены.
Что в значительной степени суммирует все это, и, на мой взгляд, это вполне разумно.
Ответ 2
Я часто предпочитаю создавать утилиты вне моих классов, когда они являются специфичными для приложения.
Приложение обычно находится в другом контексте, а затем двигатели, работающие под ним. Если мы возьмем пример веб-браузера, три метода очистки будут принадлежать веб-движку, так как это необходимая функциональность, которую трудно реализовать где-либо еще, однако ClearEverything() определенно более специфичен для приложений. В этом случае у вашего приложения может быть небольшое диалоговое окно с кнопкой "Очистить все", чтобы помочь пользователю быть более эффективным. Возможно, это не то, что захочет повторить другое приложение, использующее ваш движок веб-браузера, и поэтому его использование в классе двигателей будет просто более беспорядочным.
Другой пример - это математические библиотеки. Часто имеет смысл иметь более сложные функции, такие как среднее значение или стандартное деривация, реализованное как часть математического класса. Однако, если у вас есть специальный способ вычисления определенного типа среднего значения, который не является стандартной версией, он, вероятно, должен быть вне вашего класса и части полезной библиотеки, специфичной для вашего приложения.
Я никогда не был большим поклонником сильных жестко закодированных правил, чтобы так или иначе реализовывать вещи, часто это вопрос идеологии и принципов.
М.
Ответ 3
Функции, не являющиеся членами, обычно используются, когда разработчик библиотеки хочет писать двоичные операторы, которые могут быть перегружены по любому аргументу с типом класса, поскольку, если вы сделаете их членами класса, вы можете перегружать только второй аргумент (первый неявно является объектом этого класса). различные арифметические операторы для complex
, возможно, являются окончательным примером для этого.
В примере, который вы цитируете, мотивация имеет другой вид: использовать наименее связанный дизайн, который по-прежнему позволяет выполнять работу.
Это означает, что в то время как clearEverything
может (и, если быть откровенным, было бы весьма вероятно) стать членом, мы не делаем его одним, потому что это не технически должно быть. Это покупает вам две вещи:
- Вам не нужно принимать ответственность за использование метода
clearEverything
в вашем общедоступном интерфейсе (после того, как вы отправите его на один, вы вышли замуж за него на всю жизнь).
- Количество функций с доступом к
private
членам класса ниже, поэтому любые изменения в будущем будут легче выполнять и с меньшей вероятностью вызвать ошибки.
Тем не менее, в этом конкретном примере я чувствую, что концепция заходит слишком далеко, и за такую "невиновную" функцию я стремлюсь сделать ее членом. Но концепция звучит, и в сценариях "реального мира", где все не так просто, это будет иметь гораздо больше смысла.
Ответ 4
Локальность и позволяющая классу предоставлять "достаточные" функции при сохранении инкапсуляции - это некоторые вещи, которые следует учитывать.
Если WebBrowser
повторно используется во многих местах, зависимости/клиенты могут определять несколько удобных функций. Это позволяет вашим классам (WebBrowser
) легко и легко управлять.
Обратное будет заключаться в том, что WebBrowser
становится приятным для всех клиентов и просто становится некоторым монолитным зверем, который трудно изменить.
Вы обнаружите, что класс не имеет функциональности после его использования в нескольких сценариях? Создаются ли шаблоны в ваших удобных функциях? Лучше (IMO) отсрочить формальное расширение интерфейса класса до появления шаблонов, и есть веская причина добавить эту функциональность. Минимальный класс легче поддерживать, но вы не хотите избыточных реализаций повсюду, потому что это подталкивает нагрузку на обслуживание на ваших клиентов.
Если ваши удобные функции сложны для реализации, или существует общий случай, который может значительно повысить производительность (например, чтобы очистить поточную безопасную коллекцию с помощью одной блокировки, а не один элемент за раз с блокировкой каждый раз), тогда вы также можете рассмотреть этот случай.
Также будут случаи, когда вы осознаете, что что-то действительно отсутствует в WebBrowser
, когда вы его используете.