Как не членские функции улучшают инкапсуляцию

Я прочитал статью Скотта Майерса по этому вопросу и довольно смущен тем, о чем он говорит. У меня есть 3 вопроса.

Вопрос 1

Чтобы объяснить подробно, предположим, что я пишу простой vector class vector с такими методами, как push_back , insert и operator [] . Если бы я следовал алгоритму Мейерса, я бы получил все функции друзей, не являющиеся членами. У меня будет векторный class с несколькими частными членами и многими функциями друзей, не являющимися членами. Это то, о чем он говорит?

вопрос 2

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

 class Point { public: int getXValue() const; int getYValue() const; void setXValue(int newXValue); void setYValue(int newYValue); private: ... // whatever... }; 

Если следовать его алгоритму, методы setXXXX должны быть setXXXX . Мой вопрос в том, как это увеличивает инкапсуляцию? Он также говорит

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

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

Вопрос 3

Цитируя его алгоритм

 else if (f needs type conversions on its left-most argument) { make fa non-member function; if (f needs access to non-public members of C) make fa friend of C; } 

То, что он имел в виду под f, нуждается в преобразованиях типов по его самому левому аргументу ? Он также говорит следующее в статье.

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

Этот и приведенный выше алгоритмы противоречивы, верно?

Вопрос 1

В этом случае алгоритм Мейерса даст вам функции-члены:

  • Нужно ли им быть виртуальным? Нет.
  • Являются ли они operator<< или operator>> ? Нет.
  • Нужны ли им преобразования типов? Нет.
  • Могут ли они быть реализованы с точки зрения публичного интерфейса? Нет.
  • Поэтому сделайте их членами.

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

вопрос 2

Функции SetXXXX должны иметь доступ к внутреннему (закрытому) представлению classа, поэтому они не могут быть SetXXXX друзьями; поэтому, утверждает Майерс, они должны быть членами, а не друзьями.

Инкапсуляция возникает, скрывая детали того, как реализуется class; вы определяете открытый интерфейс отдельно от частной реализации. Если вы затем придумаете лучшую реализацию, вы можете изменить ее, не меняя общеansible интерфейс, и любой код с использованием classа будет продолжать работать. Таким образом, «количество функций, которые могут быть сломаны» Мейерса, учитывает функции члена и друга (которые мы можем легко отследить, посмотрев определение classа), но не любые функции-члены, не являющиеся членами, используя class через его открытый интерфейс.

Вопрос 3

На это был дан ответ .

Важными моментами, чтобы убрать советы Мейерса, являются:

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

Значение f нуждается в преобразованиях типов на нем влево-большинстве аргументах:

рассмотрите следующий сценарий:

 Class Integer { private: int num; public: int getNum( return num;) Integer(int n = 0) { num = n;} Integer(const Integer &rhs)) { num = rhs.num ;} Integer operator * (const Integer &rhs) { return Integer(num * rhs.num); } } int main() { Integer i1(5); Integer i3 = i1 * 3; // valid Integer i3 = 3 * i1 ; // error } 

В приведенном выше коде i3 = i1 * 3 эквивалентно this->operator*(3) который действителен, поскольку 3 неявно преобразован в Integer.

Где, как и в дальнейшем, i3 = 3 * i1 эквивалентно 3.operator*(i1) , согласно правилу, когда оператор перегрузки u, использующий функцию-член, вызывающий объект должен быть одного classа. но вот его не то.

Чтобы сделать работу Integer i3 = 3 * i1 можно определить функцию, не являющуюся членом, следующим образом:

 Integer operator * (const Integer &lhs , const Integer &rhs) // non-member function { return Integer(lhs.getNum() * rhs.getNum()); } 

Я думаю, вы получите представление из этого примера …..

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

 else if (f can be implemented via C's public interface) make fa non-member function; 

Но вы не можете реализовать такие методы, как push_back , insert или operator[] через открытый интерфейс. Это открытый интерфейс. Возможно ли реализовать push_back с точки зрения insert , но в значительной степени, какой публичный интерфейс вы собираетесь использовать для таких методов?

Кроме того, случаи дружбы с функциями, не являющимися членами, являются действительно особыми случаями, поскольку я вижу это, operator<< и operator>> и преобразования типов будут требовать очень точных и нефильтрованных данных из classа. Эти методы, естественно, очень инвазивные.

Хотя я не поклонник доктора Доббса или любого из заявленных «гуру C ++», я думаю, что в этом случае вы могли бы дважды угадать свою собственную реализацию. Алгоритм Скотта Мейера кажется мне разумным.

Взгляните на алгоритмы STL. sort , copy , transform т. д. работать с iteratorами и не являются функциями-членами.

Вы также ошибаетесь в его алгоритме. Функции set и get не могут быть реализованы с помощью открытого интерфейса Point.

Вопрос 2

Скотт Майерс также предложил следующую вещь, если вы помните:

-> Держите интерфейс classа полным и минимальным.

См. Следующий сценарий:

 class Person { private: string name; unsigned int age; long salary; public: void setName(string);// assme the implementation void setAge(unsigned int); // assme the implementation void setSalary(long sal); // assme the implementation void setPersonData() { setName("Scott"); setAge(25); selSalary(50000); } } 

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

  void setPersonData(Person &p) { p.setName("Scott"); p.setAge(25); p.selSalary(50000); } 

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

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

Поэтому руководство, которое я бы извлек из этого: используйте здравый смысл, но не бойтесь свободных функций. Они не делают ваш код на C ++ менее объектно-ориентированным.


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

В случае Point, в частности, вы можете получить соблазн сохранить данные как int coords[2] вместо этого, и в этом отношении геттеры и сеттеры могут иметь значение (но можно также всегда учитывать простоту использования и легкость использования реализация).

Но если вы переходите к более сложным classам, они должны что- то делать (некоторые основные функции), кроме как просто предоставить доступ к их данным.


Когда дело доходит до вектора, некоторые его методы могут быть свободными функциями: присваивать (в терминах clear + insert), at, back, front (в терминах size + operator[] ), empty (с точки зрения размера или начала / end), pop_back ( pop_back + size), push_back (insert + size), end (начало + размер), rbegin и rend (начало и конец).

Но если принять строго, это может привести к довольно запутывающим интерфейсам, например

  for (vector::iterator it = v.begin(); it != end(v); ++it) 

Кроме того, здесь нужно было бы рассмотреть возможности других контейнеров. Если std :: list не может реализовать конец как свободную функцию, то std :: vector также не должен (шаблоны нуждаются в едином шаблоне для итерации по контейнеру).

Опять же, используйте здравый смысл.

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

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

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

То, что он имел в виду под f, нуждается в преобразованиях типов по его самому левому аргументу?

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

  • Функция с тем же именем, но другая подпись в производном classе
  • Вызов библиотеки C ++ в C #
  • Разделительная строка с разделителями-запятыми -> FUNCTION db.CHARINDEX не существует
  • Есть ли способ использовать два оператора «...» в функции из R?
  • 'foo' не был объявлен в этой области c ++
  • Разрешение перегрузки C ++
  • Написание собственной функции квадратного корня
  • объекты data.table, назначенные с помощью: = из функции, не напечатанной
  • Как объяснить обратные вызовы на простом английском языке? Как они отличаются от вызова одной функции от другой функции?
  • Функция возвращает None без оператора return
  • Как подсчитать текст другого шрифта в excel
  • Interesting Posts

    Привязка назад-kill-word к Ctrl + w

    Принудительное использование плавающей запятой в .NET?

    Windows 10 Search не может найти ЛЮБЫЕ приложения. Даже калькулятор

    Что происходит с объявленной, неинициализированной переменной в C? Имеет ли это значение?

    Угловой фильтр работает, но приводит к тому,

    Как переносить IPv6 в m0n0wall?

    Как преобразовать .iso в .mp4 без установки с помощью ffmpeg

    Как указать байтовый литерал в Java?

    Специальная подсветка синтаксиса Vim, включая синтаксис другого языка в указанном диапазоне

    Увеличьте количество «наиболее используемых» приложений в Windows 10

    Передача файлов через Wi-Fi быстро – Интернет через Wi-Fi медленно

    Как связать вычислительную мощность старых компьютеров вместе?

    Как преобразовать массив объектов в строковый массив в Java

    CGEventPost – возможная ошибка при имитации событий клавиатуры?

    CUDA определяет streamи на блок, блоки на каждую сетку

    Давайте будем гением компьютера.