Why does C++ not allow inherited friendship?

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 ...

86
задан 7 revs, 3 users 91% 23 May 2017 в 11:55
поделиться

6 ответов

Потому что я могу писать 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);
        }
};
83
ответ дан 24 November 2019 в 08:03
поделиться

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

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 может быть защищенным или может применять протокол доступа с ограничением времени выполнения.

9
ответ дан 24 November 2019 в 08:03
поделиться

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

0
ответ дан 24 November 2019 в 08:03
поделиться

Потому что в этом нет необходимости.

Использование ключевого слова friend само по себе подозрительно. С точки зрения сцепления это худшие отношения (намного опережающие наследование и композицию).

Любые изменения во внутреннем устройстве класса могут повлиять на друзей этого класса ... вы действительно хотите неизвестное количество друзей? Вы даже не смогли бы перечислить их, если бы те, кто унаследовал от них, тоже могли быть друзьями, и вы бы каждый раз подвергались риску взломать код своих клиентов, что, конечно же, нежелательно.

Я открыто признаю, что для домашних заданий / домашних проектов зависимость часто не рассматривается. Для небольших проектов это не имеет значения. Но как только несколько человек работают над одним и тем же проектом, и это разрастается до десятков тысяч строк, вам нужно ограничить влияние изменений.

Это приводит к очень простому правилу:

Изменение внутренней структуры класса должно влиять только на сам класс

Конечно, вы, вероятно, повлияете на его друзей, но здесь есть два случая:

  • друг бесплатная функция: в любом случае, вероятно, больше функции-члена (я думаю, здесь std :: ostream & operator << (...) , которая не является членом чисто случайно языковых правил
  • друг class? вам не нужны классы друзей в реальных классах.

Я бы порекомендовал использовать простой метод:

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

Этот простой шаблон Key позволяет вам объявить друга (в некотором смысле) фактически не предоставляя ему доступа к вашим внутренним устройствам, тем самым изолируя его от изменений.Кроме того, он позволяет этому другу одалживать свой ключ опекунам (например, детям), если это необходимо.

2
ответ дан 24 November 2019 в 08:03
поделиться

Производный класс может наследовать только то, что является «членом» базы. Объявление друга не член класса дружбы.

$ 11.4 / 1- "... Имя друга не в рамках класса, а друг не звонит с участником операторы доступа (5.2.5), если это не член другого класса ».

$ 11.4 -« Кроме того, поскольку базовое предложение класса друзей не входит в его объявления членов, базовое предложение класса друзей не может получить доступ к имена частных и охраняемых члены из класса предоставления дружба. "

и далее

10 $.3 / 7- "[Примечание: виртуальный спецификатор подразумевает членство, поэтому виртуальный функция не может быть не членом (7.1.2) функция. И виртуальная функция не может быть статическим членом, поскольку виртуальный вызов функции зависит от конкретного объект для определения, какая функция вызывать. Объявлена ​​виртуальная функция в одном классе можно объявить другом в другом классе. ] "

Поскольку« друг »изначально не является членом базового класса, как он может быть унаследован производным классом?

0
ответ дан 24 November 2019 в 08:03
поделиться

Стандарт C++, раздел 11.4/8

Дружба не является ни наследуемой, ни транзитивной.

Если дружба будет передаваться по наследству, то класс, который не должен быть другом, внезапно получит доступ к внутренним компонентам вашего класса, что нарушит инкапсуляцию.

7
ответ дан 24 November 2019 в 08:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: