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


почему дружба, по крайней мере, необязательно наследуется В C++? Я понимаю, что транзитивность и рефлексивность запрещены по очевидным причинам (я говорю это только для того, чтобы предотвратить простые ответы на часто задаваемые вопросы), но отсутствие чего-то вроде 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. */ };

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

9 79

9 ответов:

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

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

Я думаю, что ответ на ваш первый вопрос в этом вопросе:

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

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

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

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

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

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

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

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

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

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

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

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

  • функция friend free: вероятно, больше функции-члена в любом случае (я думаю 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- " также, потому что базовый пункт класса друга не является частью его объявления членов, базовое предложение из класса friend не удается получить доступ к имена частных и охраняемых члены класса, предоставление дружба."

и далее

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

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

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

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

друг хорош в наследовании, как интерфейс стиля для контейнера Но для меня, как говорят первые, 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++ - это отсутствие очень хорошей детализации контролировать весь доступ из любого места для всего :

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

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

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

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

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