TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.37K subscribers
Post #1010 3.84K
​​Доступ к приватным членам. Други-функции.
#новичкам

В этой серии постов мы рассматриваем в том числе легальные способы получения доступа к приватным членам.

С++ - вообще амбассадор языковых средств обходов ограничений. Есть константный объект, которых хочется изменить? Пожалуйста, 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
  • ❤ 23
  • 🔥 8
  • 👍 6
  • 😁 2
  • 🤯 1
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →