Почему C ++ не позволяет наследственную дружбу? - PullRequest
85 голосов
/ 25 августа 2010

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

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

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

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

Ответы [ 10 ]

80 голосов
/ 25 августа 2010

Потому что я могу написать 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);
        }
};
42 голосов
/ 19 июня 2014

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

Я думаю, что ответ на ваш первый вопрос заключается в следующем: "Есть ли у друзей вашего отца доступвашим рядовым? "

9 голосов
/ 25 августа 2010

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

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

7 голосов
/ 25 августа 2010

C ++ Standard, раздел 11.4 / 8

Дружба не наследуется и не является переходной.

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

2 голосов
/ 25 августа 2010

Потому что это просто не нужно.

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

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

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

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

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

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

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

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

class Example;

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

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

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

0 голосов
/ 16 февраля 2019

Простая логика: «У меня есть подруга Джейн.То, что мы вчера стали друзьями, не делает всех ее друзей моими.

Мне все еще нужно утвердить эту индивидуальную дружбу, и уровень доверия будет соответствующим.

0 голосов
/ 08 декабря 2016

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

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
    }
};

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

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

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

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

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

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};
0 голосов
/ 26 ноября 2014

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

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

0 голосов
/ 25 августа 2010

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

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

$ 11,4 - «Кроме того, потому что базовое предложение класс друга не является частью его декларации членов, базовое предложение класс друга не может получить доступ к имена частных и охраняемых Члены из класса предоставления дружба. "

и далее

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

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

0 голосов
/ 25 августа 2010

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

...