Вопрос:

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

c++ inheritance language-design friend

46426 просмотра

10 ответа

2162 Репутация автора

Почему дружба по крайней мере необязательно наследуется в 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. */ };

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

Автор: Jeff Источник Размещён: 24.08.2010 10:55

Ответы (10)


0 плюса

227356 Репутация автора

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

Автор: Oliver Charlesworth Размещён: 24.08.2010 10:58

79 плюса

198046 Репутация автора

Решение

Потому что я могу написать 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);
        }
};
Автор: Martin York Размещён: 25.08.2010 12:45

7 плюса

2601 Репутация автора

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

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

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

Автор: David Размещён: 25.08.2010 12:59

0 плюса

19193 Репутация автора

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

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

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

и далее

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

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

Автор: Chubsdad Размещён: 25.08.2010 01:59

8 плюса

110127 Репутация автора

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

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

Автор: Potatoswatter Размещён: 25.08.2010 04:46

2 плюса

203090 Репутация автора

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

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

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

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

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

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

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

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

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

class Example;

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

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

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

Автор: Matthieu M. Размещён: 25.08.2010 06:36

40 плюса

13230 Репутация автора

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

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

Автор: wilx Размещён: 19.06.2014 10:33

0 плюса

1 Репутация автора

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

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

Автор: abdul rizwan Размещён: 26.11.2014 12:23

0 плюса

1 Репутация автора

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

Друг Thing может стать другом Thing. *, чтобы предоставить доступ всем детям Thing.

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

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

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

class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from 
             //another object instance
             anotherObject)->test(); 
         }
};
Автор: zep Размещён: 08.12.2016 05:00

0 плюса

1 Репутация автора

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

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

Автор: Robert Hamm Размещён: 16.02.2019 05:21
Вопросы из категории :
32x32