Простой дизайн выбора? - PullRequest
1 голос
/ 25 мая 2011

Скажем, у меня есть класс с private data member n и public get_n() функцией. Например, при перегрузке оператора вывода я могу использовать get_n() или сделать его другом и использовать n Есть ли «лучший» выбор? И если так, то почему? Или разница будет оптимизирована? Спасибо.

Ответы [ 5 ]

4 голосов
/ 25 мая 2011

Используйте get_n, поскольку это не правильное использование друга . И если get_n является простым return n, компилятор, скорее всего, автоматически inline сделает это.

2 голосов
/ 25 мая 2011

Я отвечу на ваш вопрос вопросом:

  • Почему вы вообще создали общедоступную get_n()?
1 голос
/ 25 мая 2011

Вы уже получили много противоречивых ответов, так что вам, несомненно, нужен еще один, который противоречит почти всем из них.

С точки зрения эффективности, это вряд ли что-то изменит.Функция, которая просто возвращает значение, несомненно, будет сгенерирована inline, если вы специально не запретите это, отключив all оптимизацию.

Это оставляет только вопрос о том, что предпочтительнее с точки зрения дизайна.По крайней мере, IMO, это обычно , предпочтительнее, чем , а не , в первую очередь get_n.Как только вы удалите эту проблему с дизайном, вопрос, который вы задали, просто исчезает: поскольку get_n для начала не существует, вы не можете написать другой код, зависящий от него.

все еще оставляет небольшой вопрос о том, как вы должны делать вещи, хотя.Это (конечно) приводит к большему количеству вопросов.В частности, что представляет собой n?Я понимаю, что вы, вероятно, приводите гипотетический пример, но хороший дизайн (в данном случае) зависит от знания немного больше о том, что такое n и как оно используется, а также о типе n членаи как it также предполагается использовать.

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

class whatever { 
    int n;

    friend std::ostream &operator<<(std::ostream &os, whatever const &w) { 
        return os << w.n;
    }
};

Простой, простой и эффективный.

Если, однако, n является членом того, что вы ожидаете использовать (или бытьиспользуется) в качестве базового класса, тогда вы обычно хотите использовать «виртуальную виртуальную» функцию:

class whatever { 
    int n;

    virtual std::ostream &write(std::ostream &os) { 
        return os << n;
    }    

    friend std::ostream &operator<<(std::ostream &os, whatever const &w) { 
        return w.write(os);
    }
};

Обратите внимание, однако, что это предполагает, что вы заинтересованы в выписывании всего объекта, и это простобывает, что, по крайней мере, в текущей реализации это означает выписывание значения n.

Что касается того, почему вы должны так поступать, есть несколько простых принципов, которые, я думаю, следует соблюдать:

  1. Либо сделайте что-нибудь действительно приватным, либо сделайте его публичным.Закрытый член с открытым get_n (и, как правило, с открытым set_n также) может потребоваться для JavaBeans (для одного примера), но все еще является действительно плохой идеей и демонстрирует грубое неправильное понимание ориентации объекта или инкапсуляции, не говоря уже о создании совершенно некрасивого кода.
  2. Скажите, не спрашивайте.Публичный get_n часто означает, что вы в конечном итоге получаете клиентский код, который выполняет цикл чтения / изменения / записи, а объект выступает в роли тупого контейнера данных.Как правило, предпочтительно преобразовать это в одну операцию, в которой клиентский код описывает желаемый результат, а сам объект выполняет чтение / изменение / запись для достижения этого результата.
  3. Минимизируйте интерфейс.Вы должны стремиться к тому, чтобы каждый объект имел наименьший возможный интерфейс, не причиняя чрезмерной боли пользователям.Исключение публичной функции, такой как get_n, почти всегда само по себе хорошо, независимо от того, хороша ли она для инкапсуляции.

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

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

Если объект поддерживает ввод и / или вывод, то вход и выход являются частямиинтерфейс этого объекта , и все, что реализует этот интерфейс, является частью объекта . only другая возможность состоит в том, что тип объекта не не поддерживает ввод и / или вывод.

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

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

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

0 голосов
/ 25 мая 2011

Может ли оператор быть реализован без использования friend?Да, не используйте friend.Не делать friend.

0 голосов
/ 25 мая 2011

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

...