Не являющиеся членами не дружественные функции против частных функций

Херб Саттер сказал, что наиболее объектно-ориентированный способ написания методов на C ++ - это использование не связанных с другом функций, не являющихся друзьями. Должно ли это означать, что я должен использовать приватные методы и превращать их в функции, не являющиеся членами-друзьями? Любые переменные-члены, которые могут понадобиться этим методам, могут быть переданы как параметры.

Пример (до):

<code>class Number {
 public:
  Number( int nNumber ) : m_nNumber( nNumber ) {}
  int CalculateDifference( int nNumber ) { return minus( nNumber ); }
 private:
  int minus( int nNumber ) { return m_nNumber - nNumber; }
  int m_nNumber;
};
</code>

Пример (после):

<code>int minus( int nLhsNumber, int nRhsNumber ) { return nLhsNumber - nRhsNumber; }
class Number {
 public:
  Number( int nNumber ) : m_nNumber( nNumber ) {}
  int CalculateDifference( int nNumber ) { return minus( m_nNumber, nNumber ); }
 private:
  int m_nNumber;
};
</code>

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

Ответы на вопрос(4)

на им отвечая на другой вопрос.

Такие правила обычно бывают хорошими слугами и плохими хозяевами, в этом есть обмен. Будет ли результат более приемлемым, если вы примените предложенное преобразование? Зачем? Я считаю, что предполагаемое преимущество заключается в том, что, уменьшая количество методов, непосредственно работающих с частными данными объекта, вы сможете легче понять его поведение и, следовательно, упростить его поддержку.

Я не верю, что твой последующий пример достигает этой цели. [Вы можете заметить, что приведенный выше пример «после» не будет компилироваться в любом случае, но это уже другая история.] Если мы настроим его для реализации внешних функций исключительно с точки зрения открытых методов, а не внутреннего состояния (добавьте метод доступа для значения) тогда, я думаю, у нас была некоторая выгода. Достаточно ли этого, чтобы оправдать работу? Мое мнение: нет. Я думаю, что преимущества предложенного перехода к внешней функции становятся намного больше, когда методы обновляют значения данных в объекте. Я хотел бы внедрить небольшое количество мутантов, поддерживающих неизменную структуру, и реализовать основные «бизнес» методы с точки зрения таковых - экстернализация этих методов - это способ гарантировать, что они могут работать только с точки зрения мутаторов.

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

Я настоятельно рекомендую прочитать Эффективный C ++ от Скотта Мейерса которые объясняют, почему вы должны это делать и когда это уместно.

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

в которой вы находитесь. Когда я пишу свой код, я действительно ставлю толькоpublic а такжеprotected функционирует в классах. Остальное - это детали реализации, являющиеся частью файла .cpp.

Я часто использую идиому pImpl. На мой взгляд, оба подхода имеют следующие преимущества:

Чистый дизайн интерфейса

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

Отделение от реализации

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

Скрыть зависимости от других классов, которые используют ваш интерфейс

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

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

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

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

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

class Number { // small simple interface: accessor to constant data, constructor
public:
  explicit Number( int nNumber ) : m_nNumber( nNumber ) {}
  int value() const { return m_nNumber; }
private:
  int m_nNumber;
};
Number operator+( Number const & lhs, Number const & rhs ) // Add addition to the interface
{
   return Number( lhs.value() + rhs.value() );
}
Number operator-( Number const & lhs, Number const & rhs ) // Add subtraction to the interface
{
   return Number( lhs.value() - rhs.value() );
}

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

Сложная часть (не в упрощенном примере выше) определяет, какой интерфейс вам наименее нужен. Статья GotW # 84), на который ссылается предыдущий вопрос, является отличным примером. Если вы прочитаете его подробно, вы обнаружите, что вы можете значительно сократить количество методов в std :: basic_string, сохраняя при этом те же функциональность и производительность. Количество будет уменьшено с 103 до 32 членов. Это означает, что изменения реализации в классе затронут только 32 вместо 103 членов, и, поскольку интерфейс сохранен, 71 свободная функция, которая может реализовать остальную функциональность в терминах 32 членов, не должна будет изменяться.

Это важный момент: он более инкапсулирован, так как вы ограничиваете влияние изменений реализации на ко

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

class ReallyComplex
{
public:
   ReallyComplex& operator+=( ReallyComplex const & rhs );
};
ReallyComplex operator+( ReallyComplex const & lhs, ReallyComplex const & rhs )
{
   ReallyComplex tmp( lhs );
   tmp += rhs;
   return tmp;
}

Легко заметить, что неважно, как оригиналoperator+= выполняет свою задачу, свободныйoperator+ выполняет свои обязанности правильно. Теперь, с любыми изменениями в классе,operator+= нужно будет обновить, но внешнийoperator+ останется нетронутой до конца жизни.

Приведенный выше код является общим шаблоном, хотя обычно вместо полученияlhs Операнд постоянная ссылка и создание временного объекта внутри, его можно изменить так, чтобы параметр сам был копией значения, помогая компилятору с некоторыми оптимизациями:

ReallyComplex operator+( ReallyComplex lhs, ReallyComplex const & rhs )
{
   lhs += rhs;
   return lhs;
}

ВАШ ОТВЕТ НА ВОПРОС