Как задается вопрос...
В чем разница между:
class MyClass
{
public:
MyClass(){
m_a = 0;
}
private:
int m_a;
friend void set_a(MyClass &a);
};
void set_a(MyClass &a)
{
std::cout << a.m_a << std::endl;
a.m_a = 500;
std::cout << a.m_a << std::endl;
}
int main(void) {
MyClass my_class_instance;
set_a(my_class_instance);
system("pause");
}
а также:
class MyClass
{
public:
MyClass(){
m_a = 0;
}
void set_a(){
std::cout << this->m_a << std::endl;
this->m_a = 500;
std::cout << this->m_a << std::endl;
}
private:
int m_a;
};
int main(void) {
MyClass my_class_instance;
my_class_instance.set_a();
system("pause");
}
Является ли это просто предпочтительной структурой функции или существуют реальные измеримые различия? Из того, что я могу сказать, обе функции достигают одинаковых результатов во всех обстоятельствах, за исключением случаев, когда у вас было несколько перегрузок для первого примера, которые занимали разные типы объектов.
Поскольку C++ часто задает вопрос: используйте члена, когда можете, и друга, когда вам нужно.
Есть ситуации, когда делать friend
свободный функция является предпочтительным, большинство ситуаций, связанных с тем, что первый параметр функции члена всегда этого класса (его скрытый *this
параметр).
Одним из примеров является перегрузка арифметических операторов:
Предположим, вы пишете complex
класс, который представляет собой комплексные числа. Используя член operator+()
вы можете написать выражения типа complex + float
, но не float + complex
. Но вы можете сделать это со свободной формой operator+
:
class complex
{
...
friend complex operator+( float f , complex c );
};
Весь этот вопрос сводится к "Почему я должен использовать друзей в C++?". Ответ заключается в том, что при правильном использовании друзья усиливают инкапсуляцию. Это часто задаваемые вопросы:
Неужели друзья нарушают инкапсуляцию?
Конечно, ваш пример слишком короткий и слишком абстрактный. Некоторые лучшие, реальные примеры жизни, которые я мог придумать из головы, связаны с итераторами. У вас может быть много объектов-итераторов, относящихся только к одному объекту-контейнеру, и вы можете захотеть, чтобы итератор мог получить доступ к частным переменным-членам контейнера. В то же время вы не хотите, чтобы контейнер отображал эти переменные для остального мира.
Такой дизайн может быть отлично реализован с помощью функции friend
.
Многие люди защищают то, что делают методы доступа, вы можете на более позднем этапе развития помешать неправильному доступу к переменным-членам (или даже полностью изменить переменные-члены), не нарушая ваших (правильных) клиентов.
Один классический случай
class ComplexNumber {
double real, imaginary;
public:
double re() { return re; }
double setRe(double v) { return re = v; }
// and so on ...
};
в один прекрасный день вы обнаружите, при каком-то обслуживании, что вам нужны полярные координаты для этого числа, поэтому вы добавляете методы
double rho() { /* calculate rho */ }
double theta() { /* calculate theta */ }
double setRho(double v) { /* calculate real, imaginary, based on the new rho */ }
и так далее.
Позже вы обнаружите, что пользователи класса используют гораздо более полярные, чем декартовы координаты для комплексных чисел, и что конверсии являются узким местом проблемы с производительностью, поэтому вы бросаете real
и imaginary
и сохраняете rho
и theta
и меняете методы getter и setter для нового - более эффективное - хранилище для rho
, theta
, re
, im
и т.д. Все клиенты вашего класса будут перекомпилировать без проблем, потому что вы изменили свою реализацию, но сохранили свои интерфейсы стабильными.