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

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

Вопрос 1

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

Вопрос 2

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

class Point {
public:
   int getXValue() const; 
   int getYValue() const; 
   void setXValue(int newXValue);
   void setYValue(int newYValue);

private:
  ...                 // whatever...
};

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

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

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

Вопрос 3

Цитируя свой алгоритм

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;
   }

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

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

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

Ответы

Ответ 1

Вопрос 1

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

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

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

Вопрос 2

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

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

Вопрос 3

Это ответ на .

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

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

Ответ 2

Значение f нуждается в преобразованиях типов на нем, причем самый левый arg выглядит следующим образом:

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

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 перегружающий оператор, использующий функцию-член, вызывающий объект должен быть одного класса. но здесь его не так.

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

Integer operator * (const Integer &lhs , const Integer &rhs) // non-member function
    {

         return Integer(lhs.getNum() * rhs.getNum());

    }

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

Вопрос: 2

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

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

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

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() является функцией-членом, но в конечном итоге то, что ее выполнение также может быть достигнуто за счет того, что она не является функцией-членом, как это, и она будет поддерживать интерфейс класса минимальным и не будет раздувать класс с большим количеством функций-членов без необходимости.

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

Ответ 6

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

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

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


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

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

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


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

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

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

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

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

Ответ 7

Он специально говорит "не-член не дружественные функции" (акцент мой). Если вам нужно будет сделать функцию non-member фальшивой, его алгоритмы говорят, что она должна быть функцией-членом, если только оператор → или оператор < или нуждается в преобразованиях типов по своему левому аргументу.

Ответ 8

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

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

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

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