Почему С++ не позволяет унаследовать дружбу?

Почему дружба, по крайней мере, необязательно наследуется на С++? Я понимаю, что транзитивность и рефлексивность запрещены по очевидным причинам (я говорю это только, чтобы отбросить простые ответы на часто задаваемые вопросы), но отсутствие чего-то в духе virtual friend class Foo; меня озадачивает. Кто-нибудь знает историческую предысторию этого решения? Была ли дружба действительно просто ограниченным взломом, который с тех пор нашел свое место в нескольких неясных респектабельных целях?

Изменить для пояснения:. Я говорю о следующем сценарии, а не о том, где дети из A подвергаются воздействию либо B, либо как B, так и его детей. Я также могу себе представить, что, возможно, предоставил доступ к переопределению функций друга и т.д.

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. */ };

Принятый ответ: как состояния Loki, эффект можно моделировать более или менее, создавая защищенные функции прокси в дружественных базовых классах, поэтому нет никакой строгой необходимости в предоставлении дружбы классу или виртуальному методу. Мне не нравится потребность в шаблонных прокси-серверах (которые эффективно подкрепляются базой), но я полагаю, что это считалось предпочтительным по сравнению с языковым механизмом, который скорее всего будет злоупотреблять большую часть времени. Я думаю, что, вероятно, время, которое я купил, и прочитал Stroupstrup Дизайн и эволюция С++, которые я видел достаточно людей здесь рекомендую, чтобы лучше понять эти вопросы...

Ответ 1

Потому что я могу написать 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);
        }
};

Ответ 2

Почему дружба, по крайней мере, необязательно наследуется в С++?

Я думаю, что ответ на ваш первый вопрос в этом вопросе: "У ваших друзей-друзей есть доступ к вашим рядовым?"

Ответ 3

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

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 pennies despite not being a friend
    int &get_cash( hot_girl &x ) { return x.get_cash(); }
};

Это позволяет более тонкое управление, чем дополнительная транзитивность. Например, get_cash может быть protected или может принудительно использовать протокол ограниченного времени выполнения.

Ответ 4

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

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

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

Ответ 5

Потому что это просто лишний.

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

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

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

Это очень простое правило:

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

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

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

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

class Example;

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

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

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

Ответ 6

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

Ответ 7

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

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

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

и далее

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

Так как "friend" в первую очередь не является членом базового класса, как он может быть унаследован производным классом?

Ответ 8

Функция Friend в классе присваивает функции extern функции. то есть extern означает, что функция была объявлена ​​и определена где-то вне класса.

Следовательно, это означает, что функция друга не является членом класса. Таким образом, наследование позволяет только наследовать свойства класса, а не внешние вещи. А также, если наследование разрешено для функций друга, то третий класс наследует.

Ответ 9

Друг хорош в наследовании, как интерфейс стиля для контейнера Но для меня, как говорят первые, С++ не хватает распространяемого наследования

class Thing;

//an interface for Thing container's
struct IThing {
   friend Thing;
   protected:
       int IThing_getData() = 0;
};

//container for thing's
struct MyContainer : public IThing {
    protected: //here is reserved access to Thing
         int IThing_getData() override {...}
};

struct Thing {
    void setYourContainer(IThing* aContainerOfThings) {
        //access to unique function in protected area 
        aContainerOfThings->IThing_getData(); //authorized access
    }
};

struct ChildThing : public Thing {
    void doTest() {
        //here the lack of granularity, you cannot access to the container.
        //to use the container, you must implement all 
        //function in the Thing class
        aContainerOfThings->IThing_getData(); //forbidden access
    }
};

Для меня проблема С++ заключается в отсутствии очень хорошей детализации для управлять всем доступом из любого места:

Друг Вещь может стать другом Вещей. * предоставить доступ ко всему ребенку Вещей

И еще, друг [named area] Thing. *, чтобы предоставить доступ для точного находятся в классе контейнера через специальную именованную область для друга.

Остановите мечту. Но теперь вы знаете интересное использование друга.

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

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};