Дружественные функции и классы


Дружественные функции - это функции, которые не являются членами класса, однако имеют доступ к его закрытым членам - переменным и функциям, которые имеют спецификатор private.

Для определения дружественных функций используется ключевое слово friend. Например, определим следующую программу:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <string>

class Auto
{
friend void drive(Auto &);
friend void setPrice(Auto &, int price);
public:
Auto(std::string autoName, int autoPrice)
{
name = autoName;
price = autoPrice;
}
std::string getName(){ return name; }
int getPrice() { return price; }

private:
std::string name; // название автомобиля
int price; // цена автомобиля
};

void drive(Auto &a)
{
std::cout << a.name << " is driven" << std::endl;
}
void setPrice(Auto &a, int price)
{
if (price > 0)
a.price = price;
}

int main()
{
Auto tesla("Tesla", 5000);
drive(tesla);
std::cout << tesla.getName() << " : " << tesla.getPrice() << std::endl;
setPrice(tesla, 8000);
std::cout << tesla.getName() << " : " << tesla.getPrice() << std::endl;

return 0;
}
Здесь определен класс Auto, который представляет автомобиль. У этого класса определены приватные закрытые переменные name (название автомобиля) и price (цена автомобиля). Также в классе объявлены две дружественные функции: drive (функция вождения автомобиля) и setPrice (функция назначения цены). Обе этих функции принимают в качестве параметра ссылку на объект Auto.

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

При этом для дружественных функций не важно, определяются они под спецификатором public или private. Для них это не имеет значения.

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

Консольный вывод программы:

Tesla is driven
Tesla : 5000
Tesla : 8000
Определение дружественных функций в классе
Дружественные функции могут определяться в другом классе. Например, определим класс Person, который использует объект Auto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <string>

class Auto;

class Person
{
public:
Person(std::string n)
{
name = n;
}
void drive(Auto &a);
void setPrice(Auto &a, int price);

private:
std::string name;
};

class Auto
{
friend void Person::drive(Auto &);
friend void Person::setPrice(Auto &, int price);
public:
Auto(std::string autoName, int autoPrice)
{
name = autoName;
price = autoPrice;
}
std::string getName() { return name; }
int getPrice() { return price; }

private:
std::string name; // название автомобиля
int price; // цена автомобиля
};

void Person::drive(Auto &a)
{
std::cout << name << " drives " << a.name << std::endl;
}
void Person::setPrice(Auto &a, int price)
{
if (price > 0)
a.price = price;
}

int main()
{
Auto tesla("Tesla", 5000);
Person tom("Tom");
tom.drive(tesla);
tom.setPrice(tesla, 8000);
std::cout << tesla.getName() << " : " << tesla.getPrice() << std::endl;

return 0;
}
Вначале определен класс Person, который представляет человека. Однако поскольку класс Person использует класс Auto, то перед классом Person идет объявление класса Auto.

Две функции из класса Person принимают ссылку на объект Auto:

1
2
void drive(Auto &a);
void setPrice(Auto &a, int price);
То есть фигурально говоря, человек водит автомобиль и назначает ему цену с помощью этих функциий.

Класс Auto определяет дружественные функции с той же сигнатурой:

1
2
friend void Person::drive(Auto &);
friend void Person::setPrice(Auto &, int price);
Причем поскольку данные функции будут определены в классе Person, то названия этих функций предваряется префиксом "Person::".

И поскольку в этих функциях предполагается использовать объект Auto, то ко времени определения этих функций все члены объекта Auto должны быть известны, поэтому определения функций находятся не в самом классе Person, а после класса Auto. И так как эти функции определены в классе Auto как дружественные, мы можем обратиться в этих функциях к закрытым членам класса Auto.

Консольный вывод программы:

Tom drives Tesla
Tesla : 8000
Дружественные классы
В случае выше класс Person использует только две функции из класса Auto. Но допустим впоследствии возникла необходимость добавить в класс Auto еще ряд дружественных функций, которые будут определены в классе Person. Либо мы можем предполагать, что класс Person будет активно использовать объекты Auto. И в этом случае целесообразно определять не отдельные дружественные функции, а определить дружественным весь класс Person:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <string>

class Auto;

class Person
{
public:
Person(std::string n)
{
name = n;
}
void drive(Auto &a);
void setPrice(Auto &a, int price);

private:
std::string name;
};

class Auto
{
friend class Person;
public:
Auto(std::string autoName, int autoPrice)
{
name = autoName;
price = autoPrice;
}
std::string getName() { return name; }
int getPrice() { return price; }

private:
std::string name; // название автомобиля
int price; // цена автомобиля
};

void Person::drive(Auto &a)
{
std::cout << name << " drives " << a.name << std::endl;
}
void Person::setPrice(Auto &a, int price)
{
if (price > 0)
a.price = price;
}

int main()
{
Auto tesla("Tesla", 5000);
Person tom("Tom");
tom.drive(tesla);
tom.setPrice(tesla, 8000);
std::cout << tesla.getName() << " : " << tesla.getPrice() << std::endl;

return 0;
}
Единственно, что в данном случае изменилось по сравнению с предыдущим примером это то, что в классе Auto определение дружественных функций было заменено определением дружественного класса:

1
friend class Person;
То есть тем самым мы опять же говорим, что класс Person - это друг класса Auto, поэтому объекты Person могут обращаться к приватным переменным класса Auto. После этого в классе Person можно обращаться к закрытым членам класса Auto из любых функций.