Класс друга
Дружественный класс в C++ может получить доступ к закрытым и защищенным членам класса , в котором он объявлен как дружественный. [1] В значительной степени дружественный класс используется для части структуры данных , представленной классом, для обеспечения доступа к основному классу, представляющему эту структуру данных. Механизм дружественных классов позволяет расширить хранилище и доступ к частям, сохраняя при этом правильную инкапсуляцию , как ее видят пользователи структуры данных.
Подобно дружественному классу, дружественная функция — это функция , которой предоставляется доступ к закрытым и защищенным членам класса, в котором она объявлена как дружественная.
Пример
[ редактировать ]В следующем примере показано использование дружественного класса для структуры данных графа , где граф представлен основным классом Graph, а вершины графа представлены классом Vertex .
#include <iostream>
#include <memory>
#include <string>
#include <unordered_set>
class Graph;
class Vertex {
public:
explicit Vertex(std::string name) : edges_(), name_(std::move(name)) {}
auto begin() const { return edges_.cbegin(); }
auto end() const { return edges_.cend(); }
const auto& name() const { return name_; }
private:
// Vertex gives access-rights to Graph.
friend class Graph;
std::unordered_set<Vertex*> edges_;
std::string name_;
};
class Graph {
public:
~Graph() {
while (!vertices_.empty()) {
auto vertex = vertices_.begin();
RemoveVertex(*vertex);
}
}
auto AddVertex(const std::string& name) -> Vertex* {
auto vertex = std::make_unique<Vertex>(name);
auto iter = vertices_.insert(vertex.get());
return vertex.release();
}
void RemoveVertex(Vertex* vertex) {
vertices_.erase(vertex);
delete vertex;
}
auto AddEdge(Vertex* from, Vertex* to) {
// Graph can access Vertex's private fields because Vertex declared Graph as
// a friend.
from->edges_.insert(to);
}
auto begin() const { return vertices_.cbegin(); }
auto end() const { return vertices_.cend(); }
private:
std::unordered_set<Vertex*> vertices_;
};
Инкапсуляция
[ редактировать ]Правильное использование дружественных классов увеличивает инкапсуляцию, поскольку позволяет расширить частный доступ к структуре данных к ее частям, которыми владеет структура данных, не разрешая приватный доступ к какому-либо другому внешнему классу. Таким образом, структура данных остается защищенной от случайных попыток взлома инвариантов структуры данных извне.
Важно отметить, что класс не может предоставить себе доступ к частной части другого класса; это нарушит инкапсуляцию. Скорее, класс предоставляет доступ к своим личным частям другому классу, объявляя этот класс дружественным. В примере с графом Graph не может объявить себя другом Vertex. Вместо этого Vertex объявляет Graph другом и таким образом предоставляет Graph доступ к своим частным полям.
Тот факт, что класс выбирает себе друзей, означает, что дружба в целом не симметрична. В примере с графиком Vertex не может получить доступ к частным полям Graph, хотя Graph может получить доступ к частным полям Vertex.
Альтернативы
[ редактировать ]Аналогичную, но не эквивалентную функцию языка предоставляет C#. internal
Ключевое слово модификатора доступа, которое позволяет классам внутри одной сборки получать доступ к закрытым частям других классов. Это соответствует пометке каждого класса как друга другого в той же сборке; Классы друзей более детализированы.
Языки программирования, в которых отсутствует поддержка дружественных классов или аналогичных языковых функций, должны будут реализовать обходные пути для достижения безопасного интерфейса на основе частей со структурой данных. Примеры таких обходных путей:
- Сделайте поля частей общедоступными. Это решение уменьшает инкапсуляцию, позволяя нарушать инварианты структуры данных извне.
- Переместите все изменяемые структурные данные из части в структуру данных и введите косвенность обратно от каждой части к ее структуре данных. Это решение меняет организацию структуры данных и увеличивает потребление памяти в тех случаях, когда в противном случае в этой информации не было бы необходимости.
Характеристики
[ редактировать ]- Дружба не симметрична – если класс
A
друг классаB
, сортB
не является автоматически другом классаA
. - Дружба не транзитивна – если класс
A
друг классаB
и классB
друг классаC
, сортA
не является автоматически другом классаC
. - Дружба не передается по наследству – если класс
Base
друг классаX
, подклассDerived
не является автоматически другом классаX
; и если классX
друг классаBase
, сортX
не является автоматически другом подклассаDerived
. Однако, если классY
друг подклассаDerived
, сортY
также будет иметь доступ к защищенным частям классаBase
, так же, как подклассDerived
делает.