#c #overloading #friend
#c #перегрузка #друг
Вопрос:
Допустим, у меня есть класс с private data member n
и public get_n()
функцией. Например, при перегрузке оператора вывода я могу либо использовать get_n()
, либо сделать его другом и использовать n
. Есть ли «лучший» выбор? И если да, то почему? Или разница будет оптимизирована? Спасибо.
Комментарии:
1. Как вы думаете, почему функция get_n() вообще заметна по сравнению с самим выводом?
Ответ №1:
Используйте get_n, поскольку это не правильное использование friend. И если get_n
это простой return n
, компилятор, скорее всего, встроит его автоматически.
Комментарии:
1. -1 для друзей, не дружественных к ООП . Это не тот случай. Популярным примером являются классы с частными конструкторами, которые имеют заводской класс в качестве друзей. Это очень хороший объектно-ориентированный дизайн, который опирается на друзей. Более подробное объяснение можно найти в C FAQ lite .
2. @Space_C0wb0y: Это не то же самое, потому что элемент данных уже общедоступен.
3. @Space_C0wb0y: Спасибо за разъяснение ( 1 за то, что мне нравятся неудачные обобщения), и я знаю об использовании friend , но в случае с этим конкретным вопросом это действительно не решение. Я собираюсь отредактировать свой ответ, чтобы он был более понятным.
4. @DeadMG: Я согласен, но в ответе говорится об этом в очень общем виде (говоря, что они не подходят для ООП , вместо они здесь неуместны ), поэтому я чувствовал себя обязанным противоречить.
Ответ №2:
Я отвечу на ваш вопрос вопросом:
- Почему вы создали public
get_n()
в первую очередь?
Комментарии:
1. Это был фиктивный пример. У меня мог бы быть простой контейнер с закрытым элементом sz и общедоступной функцией size() const.
2. @Garp: И это был риторический вопрос.
Ответ №3:
Вы уже получили много несколько противоречивых ответов, поэтому вам, несомненно, нужен еще один, который противоречит почти всем из них.
С точки зрения эффективности это вряд ли что-то изменит. Функция, которая просто возвращает значение, несомненно, будет сгенерирована встроенной, если вы специально не запретите это делать, отключив всю оптимизацию.
Остается только вопрос о том, что предпочтительнее с точки зрения дизайна. По крайней мере, ИМО, обычно предпочтительнее не иметь get_n
в первую очередь. Как только вы устраните эту проблему с дизайном, вопрос, который вы задали, просто исчезнет: поскольку get_n
начать не с чего, вы не можете написать другой код, зависящий от него.
Это все еще оставляет небольшой вопрос о том, как вы должны что-то делать. Это (конечно) приводит к большему количеству вопросов. В частности, что за вещь n
представляет? Я понимаю, что вы, вероятно, приводите гипотетический пример, но хороший дизайн (в данном случае) зависит от знания немного большего о том, что n
такое и как это используется, а также о типе, который n
является элементом, и как он также предназначен для использования.
Если n
является членом конечного класса, от которого вы не ожидаете производной, то вам, вероятно, следует использовать дружественную функцию, которая записывает n
напрямую:
class whatever {
int n;
friend std::ostream amp;operator<<(std::ostream amp;os, whatever const amp;w) {
return os << w.n;
}
};
Простой, понятный и эффективный.
Однако, если n
является членом чего-либо, что вы ожидаете использовать (или быть использованным) в качестве базового класса, то обычно вы хотите использовать «виртуальную виртуальную» функцию:
class whatever {
int n;
virtual std::ostream amp;write(std::ostream amp;os) {
return os << n;
}
friend std::ostream amp;operator<<(std::ostream amp;os, whatever const amp;w) {
return w.write(os);
}
};
Обратите внимание, однако, что это предполагает, что вы заинтересованы в написании всего объекта, и просто случается, что, по крайней мере, в текущей реализации, это означает запись значения n
.
Что касается того, почему вы должны делать что-то таким образом, есть несколько простых принципов, которым, я думаю, следует следовать:
- Либо сделайте что-то действительно закрытым, либо сделайте это общедоступным. Закрытый элемент с public
get_n
(и, как правило, также publicset_n
) может потребоваться для JavaBeans (для одного примера), но это все еще действительно плохая идея и демонстрирует грубое непонимание объектной ориентации или инкапсуляции, не говоря уже о создании совершенно уродливого кода. - Скажи, не спрашивай. Общедоступность
get_n
часто означает, что в конечном итоге вы получаете клиентский код, который выполняет цикл чтения / изменения / записи, при этом объект действует как обычный контейнер данных. Обычно предпочтительнее преобразовать это в единственную операцию, в которой клиентский код описывает желаемый результат, а сам объект выполняет чтение / модификацию / запись для достижения этого результата. - Минимизировать интерфейс. Вы должны стремиться к тому, чтобы каждый объект имел как можно меньший интерфейс, не причиняя излишней боли пользователям. Устранение общедоступной функции типа
get_n
почти всегда хорошо само по себе, независимо от того, подходит ли она для инкапсуляции.
Поскольку другие комментировали friend
функции, я добавлю свои два цента и по этому вопросу. Довольно часто можно услышать комментарии о том, что «friend следует избегать, потому что это нарушает инкапсуляцию».
Я должен категорически не согласиться и далее полагаю, что любому, кто так думает, еще предстоит поработать, чтобы научиться мыслить как программист. Программист должен мыслить в терминах абстракций, а затем реализовать эти абстракции настолько разумно, насколько это возможно в реальном мире.
Если объект поддерживает ввод и / или вывод, то ввод и вывод являются частями интерфейса этого объекта, и все, что реализует этот интерфейс, является частью объекта. Единственная другая возможность заключается в том, что тип объекта не поддерживает ввод и / или вывод.
Суть здесь довольно проста: по крайней мере, для поддержки обычных соглашений, вставляющие и извлекающие средства C должны быть написаны как свободные (не являющиеся членами) функции. Несмотря на это, вставка и извлечение являются такой же частью интерфейса класса, как и любые другие операции, и (следовательно) средство вставки / извлечения является такой же частью класса (как абстракция), как и все остальное.
Я хотел бы отметить для протокола, что это одна из причин, по которой я предпочитаю реализовывать дружественные функции, задействованные внутри класса, как я показал их выше. С логической точки зрения, они являются частью класса, поэтому хорошо, что они выглядят как часть класса.
Я повторю в последний раз для наглядности: предоставление им доступа к внутренним элементам класса никак не может нарушить инкапсуляцию, потому что на самом деле они являются частями класса — и странное требование C о том, чтобы они были реализованы как свободные функции, ни на йоту не меняет этого факта.
Комментарии:
1. Спасибо за подробное объяснение, это именно то, на что я надеялся. Ваши замечания по поводу инкапсуляции и общего использования дружественных функций очень понятны и полезны.
Ответ №4:
В этом случае наилучшей практикой для класса является реализация toString
метода, который оператор вывода использует для получения строкового представления. Поскольку это функция-член, она может обращаться ко всем данным напрямую. У этого также есть дополнительное преимущество в том, что вы можете создать этот метод vritual
, чтобы подклассы могли переопределять его, и вам нужен только один оператор вывода для базового класса.
Комментарии:
1. В нем уже есть
toString
метод — оператор вставки потока.2. @DeadMG: Оператор вставки потока не является методом (что для меня означает то же самое, что функция-член ). Это всегда должна быть свободная функция (для пользовательских типов).
Ответ №5:
Может ли оператор быть реализован без использования friend
? Да — не используйте friend
. Нет- make friend
.