Доступ к приватным членам. Други-функции.#новичкам
В этой серии постов мы рассматриваем в том числе легальные способы получения доступа к приватным членам.
С++ - вообще амбассадор языковых средств обходов ограничений. Есть константный объект, которых хочется изменить? Пожалуйста, const_cast. Застряли где-то в пучинах кода и не можете красиво выйти? Пожалуйста, goto.
В ту же копилку можно отнести друзей.
Ключевое слово friend в С++ позволяет сущности получить доступ ко всем непубличным члена класса. Сегодня рассмотрим друзей-функций.
struct Foo {
friend void foo(Foo& foo) {
std::cout << "I've stolen your secret: " << foo.a << std::endl;
}
private:
int a = 42;
};Завести друга очень просто(по крайней мере в С++). Вам нужно объявить или определить функцию внутри скоупа класса и пометить ее ключевым словом friend. Тогда класс начинает доверять вашей функции настолько, что раскрывает ей все свои секретики.
Можно также просто объявить функцию внутри класса, а определить ее вне:
struct Foo {
friend void foo(Foo& foo);
private:
int a = 42;
};
void foo(Foo& foo) {
std::cout << "I've stolen your secret: " << foo.a << std::endl;
}Разница в том, что в первом варианте функция ищется только с помощью ADL по аргументу, а во втором - по скоупу, в котором она определена. Но это уже для гиков.
В чем особенность френдов?
Их недолюбливают, но иногда без них никуда.С одной стороны, вы даете другой сущности доступ к приватным полям класса. И сразу возникает вопрос:
а зачем? Почему нельзя обойтись публичным интерфейсом?Но иногда действительно нельзя. И хрестоматийным примером здесь будет перегрузка операторов для симметричных операций.
Например, у вас есть класс для работы с комплексными числами и вы хотите определить для этого класса операцию умножения на скаляр:
class Complex {
private:
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// It works only for complex * double
Complex operator*(double scalar) const {
return Complex(real * scalar, imag * scalar);
}
// This is for double * complex
friend Complex operator*(double scalar, const Complex& c);
};
Complex operator*(double scalar, const Complex& c) {
return Complex(c.real * scalar, c.imag * scalar);
}Честный метод-operator* будет работать только в том случае, когда объект комплексного числа стоит слева. А за умножение
double * complex отвечает глобальный оператор*. И конкретную его перегрузку нужно сделать другом, чтобы он имел доступ к приватным полям.
Конкретно для этого примера есть вопросики по тому, зачем делать приватными поля комплексного числа, но тут важна суть работы с friend.
Если ваш класс работает со стандартными бинарными операторами(сравнения, арифметика) и у него есть непубличные поля, то вам может понадобиться определить эти стандартные глобальные операторы друзьями. Вот ссылочка на пример из библиотеки
Eigen. Тот же самый принцип работает, например, с оператором вывода в поток:
class Complex {
// ...
friend std::ostream& operator<<(std::ostream& os, const Complex& obj);
};
std::ostream& operator<<(std::ostream& os, const Complex& obj) {
os << "Real: " << obj.real << ", Imaginary: " << imag;
return os;
}Тоже
пример дружественной перегрузки вывода из Eigen.
Страуструп кстати дает один интересный иллюстрационный пример: в рэнджах момент окончания последовательности задается в более общем виде, чем итератор - sentinel(ограничитель). Это по сути объект любого типа, главное чтобы итератор мог уметь сравниться с ним. И вот для сравнения итератора и ограничителя может понадобиться определить дружественные операторы == и !=.
За пределами же кейсов с операторами использование функций-друзей должно подвергаться строгим проверкам на предмет попыток обойти кривой дизайн.
Value your friends. Stay cool.
#cppcore