Why is friendship not at least optionally inheritable in C++? I understand transitivity and reflexivity being forbidden for obvious reasons (I say this only to head off simple FAQ quote answers), but the lack of something along the lines of virtual friend class Foo;
puzzles me. Does anyone know the historical background behind this decision? Was friendship really just a limited hack that has since found its way into a few obscure respectable uses?
Edit for clarification: I'm talking about the following scenario, not where children of A are exposed to either B or to both B and its children. I can also imagine optionally granting access to overrides of friend functions, etc.
class A {
int x;
friend class B;
};
class B {
// OK as per friend declaration above.
void foo(A& a, int n) { a.x = n; }
};
class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };
Accepted answer: as Loki states, the effect can be simulated more or less by making protected proxy functions in friended base classes, so there is no strict need for granting friendship to a class or virtual method heirarchy. I dislike the need for boilerplate proxies (which the friended base effectively becomes), but I suppose that this was deemed preferable over a language mechanism that would more likely be misused most of the time. I think it's probably time I bought and read Stroupstrup's The Design and Evolution of C++, which I've seen enough people here recommend, to get better insight to these types of questions ...
Потому что я могу писать Foo
и его друга Bar
(таким образом, существуют доверительные отношения).
Но доверяю ли я людям, которые пишут классы, производные от Bar
?
Не совсем. Поэтому они не должны унаследовать дружбу.
Любое изменение во внутреннем представлении класса потребует модификации всего, что зависит от этого представления. Таким образом, все члены класса, а также все друзья класса потребуют модификации.
Следовательно, если внутреннее представление Foo
изменено, то Bar
также необходимо изменить (поскольку дружба тесно связывает Bar
с Foo
). Если бы дружба была унаследована, то весь класс, производный от Bar
, также был бы жестко привязан к Foo
и, таким образом, потребовал бы модификации, если внутреннее представление Foo
изменилось. Но я не знаю производных типов (и я не должен. Они даже могут быть разработаны разными компаниями и т. Д.).Таким образом, я не смог бы изменить Foo
, так как это внесет критические изменения в базу кода (поскольку я не мог изменить весь класс, производный от Bar
).
Таким образом, если дружба была унаследована, вы непреднамеренно вводите ограничение на возможность изменения класса. Это нежелательно, поскольку вы фактически делаете бесполезной концепцию общедоступного API.
Примечание: дочерний элемент Bar
может получить доступ к Foo
с помощью Bar
, просто сделайте метод в Bar
защищенным. Затем дочерний элемент Bar
может получить доступ к Foo
, вызвав его через его родительский класс.
Это то, что вам нужно?
class A
{
int x;
friend class B;
};
class B
{
protected:
// Now children of B can access foo
void foo(A& a, int n) { a.x = n; }
};
class D : public B
{
public:
foo(A& a, int n)
{
B::foo(a, n + 5);
}
};
Добавленный класс может раскрыть своего друга через функции доступа, а затем предоставить доступ через них.
class stingy {
int pennies;
friend class hot_girl;
};
class hot_girl {
public:
stingy *bf;
int &get_cash( stingy &x = *bf ) { return x.pennies; }
};
class moocher {
public: // moocher can access stingy's pennies despite not being a friend
int &get_cash( hot_girl &x ) { return x.get_cash(); }
};
Это позволяет более тонкий контроль, чем необязательная транзитивность. Например, get_cash
может быть защищенным
или может применять протокол доступа с ограничением времени выполнения.
Предположение: если класс объявляет какой-то другой класс / функцию в качестве друга, это потому, что этому второму объекту нужен привилегированный доступ к первому. Какая польза от предоставления второму объекту привилегированного доступа к произвольному количеству классов, производных от первого?
Потому что в этом нет необходимости.
Использование ключевого слова friend
само по себе подозрительно. С точки зрения сцепления это худшие отношения (намного опережающие наследование и композицию).
Любые изменения во внутреннем устройстве класса могут повлиять на друзей этого класса ... вы действительно хотите неизвестное количество друзей? Вы даже не смогли бы перечислить их, если бы те, кто унаследовал от них, тоже могли быть друзьями, и вы бы каждый раз подвергались риску взломать код своих клиентов, что, конечно же, нежелательно.
Я открыто признаю, что для домашних заданий / домашних проектов зависимость часто не рассматривается. Для небольших проектов это не имеет значения. Но как только несколько человек работают над одним и тем же проектом, и это разрастается до десятков тысяч строк, вам нужно ограничить влияние изменений.
Это приводит к очень простому правилу:
Изменение внутренней структуры класса должно влиять только на сам класс
Конечно, вы, вероятно, повлияете на его друзей, но здесь есть два случая:
std :: ostream & operator << (...)
, которая не является членом чисто случайно языковых правил Я бы порекомендовал использовать простой метод:
class Example;
class ExampleKey { friend class Example; ExampleKey(); };
class Restricted
{
public:
void forExampleOnly(int,int,ExampleKey const&);
};
Этот простой шаблон Key
позволяет вам объявить друга (в некотором смысле) фактически не предоставляя ему доступа к вашим внутренним устройствам, тем самым изолируя его от изменений.Кроме того, он позволяет этому другу одалживать свой ключ опекунам (например, детям), если это необходимо.
Производный класс может наследовать только то, что является «членом» базы. Объявление друга не член класса дружбы.
$ 11.4 / 1- "... Имя друга не в рамках класса, а друг не звонит с участником операторы доступа (5.2.5), если это не член другого класса ».
$ 11.4 -« Кроме того, поскольку базовое предложение класса друзей не входит в его объявления членов, базовое предложение класса друзей не может получить доступ к имена частных и охраняемых члены из класса предоставления дружба. "
и далее
10 $.3 / 7- "[Примечание: виртуальный спецификатор подразумевает членство, поэтому виртуальный функция не может быть не членом (7.1.2) функция. И виртуальная функция не может быть статическим членом, поскольку виртуальный вызов функции зависит от конкретного объект для определения, какая функция вызывать. Объявлена виртуальная функция в одном классе можно объявить другом в другом классе. ] "
Поскольку« друг »изначально не является членом базового класса, как он может быть унаследован производным классом?
Стандарт C++, раздел 11.4/8
Дружба не является ни наследуемой, ни транзитивной.
Если дружба будет передаваться по наследству, то класс, который не должен быть другом, внезапно получит доступ к внутренним компонентам вашего класса, что нарушит инкапсуляцию.