Чому C ++ не дозволяє успадковувати дружбу?


93

Чому дружба не є принаймні необов’язково успадковуваною в 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's The Design and Evolution of C ++ , який я бачив тут досить багато людей, щоб краще зрозуміти ці типи питань ...

Відповіді:


93

Тому що я можу писати Fooі його другові Bar(отже, існують відносини довіри).

Але чи довіряю я людям, які пишуть класи, похідні від яких Bar?
Не зовсім. Тому вони не повинні успадковувати дружбу.

Будь-яка зміна внутрішнього представлення класу вимагатиме модифікації будь-чого, що залежить від цього подання. Таким чином, усі члени класу, а також усі друзі класу потребуватимуть модифікації.

Тому, якщо внутрішнє уявлення про Fooмодифіковано, тоді Barйого також слід модифікувати (оскільки дружба тісно пов’язує Barз ним Foo). Якби дружба передалася у спадок, то всі класи, похідні від Bar, також були б тісно пов'язані Fooі, отже, вимагали модифікації, якщо Fooбуло змінено внутрішнє уявлення. Але я не маю знань про похідні типи (я також не повинен. Вони можуть навіть розроблятися різними компаніями тощо). Таким чином, я не зміг би змінитись, Fooоскільки це вводило б незмінні зміни в основу коду (оскільки я не міг змінити весь клас, похідний від Bar).

Таким чином, якщо дружба передалася у спадок, ви мимоволі вводите обмеження на можливість модифікації класу. Це небажано, оскільки ви в основному робите марною концепцію публічного API.

Примітка: Нащадок дитини Barможе отримати доступ Fooза допомогою Bar, просто зробіть метод Barзахищеним. Тоді дочірня користувача Barможе отримати доступ до a 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);
        }
};

4
Бінго. Вся справа в обмеженні шкоди, яку ви можете заподіяти, змінивши внутрішні елементи класу.
j_random_hacker

Чесно кажучи, випадок, про який я справді замислююсь, - це шаблон «Адвокат-клієнт», де проміжний продукт діє як обмежений інтерфейс для зовнішніх класів, представляючи методи обгортки базовому класу обмеженого доступу. Сказати, що інтерфейс доступний для всіх дітей інших класів, а не точного класу, було б набагато корисніше, ніж система в даний час.
Джефф,

@Jeff: Розкриття внутрішнього представництва для всіх дочірніх класів призведе до того, що код стане незмінним (він також фактично порушує інкапсуляцію, оскільки кожен, хто хотів отримати доступ до внутрішніх членів, повинен успадкувати від Bar, навіть якщо вони насправді не є Bar ).
Martin York

@Martin: Правильно, у цій схемі дружну базу можна використовувати для потрапляння до класу дружби, що може бути простим порушенням інкапсуляції у багатьох (якщо не в більшості) випадках. Однак у ситуаціях, коли дружена основа є абстрактним класом, будь-який похідний клас буде примушений реалізувати власний взаємний інтерфейс. Я не впевнений, чи вважатиметься клас `` самозванця '' за цим сценарієм, що порушує інкапсуляцію або порушує інтерфейсний контракт, якщо він не намагається сумлінно виконувати свою заявлену роль належним чином.
Джефф,

@Martin: Правильно, це той ефект, який я хочу і іноді вже фактично використовую, де A насправді є взаємно поєднаним інтерфейсом до якогось класу обмеженого доступу Z. Спільна скарга із звичайною ідіомою Адвокат-Клієнт, схоже, полягає в тому, що клас інтерфейсу A повинен зводити оболонки викликів до Z, і для того, щоб розширити доступ до підкласів, шаблон A повинен бути по суті продубльований у кожному базовому класі, наприклад B. Інтерфейс зазвичай виражає, яку функціональність модуль хоче запропонувати, а не яку функціональність в інших він хоче використати себе.
Джефф

48

Чому дружба не є, принаймні, необов’язково успадковуваною в C ++?

Я думаю, що відповідь на ваше перше запитання полягає в цьому запитанні: "Чи мають друзі вашого батька доступ до ваших рядових?"


36
Справедливості заради, це питання викликає тривожні питання щодо вашого батька. . .
iheanyi

3
Який сенс у цій відповіді? в кращому випадку сумнівний, хоча, можливо, і душевний коментар
DeveloperChris

11

Клас друзів може виставити свого друга за допомогою функцій доступу, а потім надати доступ через них.

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


@Hector: Голосування за рефакторинг! ;)
Олександр Шукаєв

7

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

Дружба не є ні успадкованою, ні транзитивною.

Якщо дружба передаватиметься у спадок, тоді клас, який не мав бути другом, раптом отримав би доступ до внутрішніх матеріалів вашого класу, що порушувало б інкапсуляцію.


2
Скажімо, Q "друзі" A, а B походить від A. Якщо B успадковує дружбу від A, то оскільки B є типом A, технічно це IS, який має доступ до рядових Q. Отже, це не відповідає на запитання з жодної практичної причини.
mdenton8

2

Бо це просто непотрібно.

Використання friendключового слова саме по собі є підозрілим. З точки зору сполучення це найгірші стосунки (набагато попереду спадщини та складу).

Будь-яка зміна внутрішньої частини класу має ризик вплинути на друзів цього класу ... ви дійсно хочете невідому кількість друзів? Ви навіть не змогли б їх перерахувати, якби ті, хто успадковує від них, також могли бути друзями, і ви ризикували б щоразу зламати код своїх клієнтів, безумовно, це не бажано.

Я вільно визнаю, що для домашніх завдань / проектів з домашніми тваринами залежність часто є далеким питанням. Для малих проектів це не має значення. Але як тільки кілька людей працюють над одним проектом, і це переростає в десятки тисяч рядків, вам потрібно обмежити вплив змін.

Це приносить дуже просте правило:

Зміна внутрішніх елементів класу має впливати лише на сам клас

Звичайно, ви, мабуть, вплинете на його друзів, але тут є два випадки:

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

Я б порекомендував використовувати простий метод:

class Example;

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

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

Цей простий Keyшаблон дозволяє оголосити друга (певним чином), фактично не надаючи йому доступу до ваших внутрішніх органів, тим самим ізолюючи його від змін. Крім того, це дозволяє цьому другові передавати свій ключ опікунам (як дітям), якщо це потрібно.


0

Припущення: Якщо клас оголошує якийсь інший клас / функцію другом, це тому, що другий сутність потребує привілейованого доступу до першого. Яка користь у наданні другому об’єкту привілейованого доступу до довільної кількості класів, похідних від першого?


2
Якби клас А хотів подружити дружбу з В та її нащадками, він міг би уникати оновлення свого інтерфейсу для кожного доданого підкласу або змушувати Б писати прохідний шаблон, що, на мою думку, в першу чергу становить половину точки дружби.
Джефф,

@Jeff: А, тоді я неправильно зрозумів ваш задуманий сенс. Я припускав, що ви мали на увазі, що Bматимете доступ до всіх класів, успадкованих від A...
Олівер Чарлсворт

0

Похідний клас може успадкувати лише те, що є "членом" бази. Декларація друга не є членом класу друзів.

$ 11,4 / 1- "... Ім'я друга не входить до сфери дії класу, і друг не викликається з операторами доступу членів (5.2.5), якщо він не є членом іншого класу."

11,4 дол. США - "Крім того, оскільки базове речення класу friend не є частиною декларацій членів, базове речення класу friend не може отримати доступ до імен приватних та захищених членів класу, що дарує дружбу."

і далі

$ 10,3 / 7 "[Примітка: віртуальний специфікатор передбачає членство, тому віртуальна функція не може бути функцією, що не є членом (7.1.2). Також віртуальна функція не може бути статичним членом, оскільки виклик віртуальної функції покладається на конкретний об'єкт для визначаючи, яку функцію викликати. Віртуальну функцію, заявлену в одному класі, можна оголосити другом в іншому класі.] "

Оскільки 'друг' не є членом базового класу, перш за все, як його можна успадкувати похідним класом?


Дружба, хоча і дається через декларації, подібні до членів, насправді не є членами стільки, скільки повідомлення про те, які інші класи можуть по суті ігнорувати класифікацію видимості для "реальних" членів. Незважаючи на те, що специфічні розділи, які ви цитуєте, пояснюють, як мова діє щодо цих нюансів та поводить поведінку самоузгодженою термінологією, все могло бути створено інакше, і ніщо вище не потрапляє в суть обґрунтування, на жаль.
Джефф

0

Функція Friend у класі присвоює функції властивість extern. тобто extern означає, що функція оголошена і визначена десь поза класом.

Отже, це означає, що функція "друг" не є членом класу. Отже, успадкування дозволяє лише успадкувати властивості класу, а не зовнішні речі. А також, якщо успадкування дозволено для функцій друзів, це успадкування третьої сторони.


0

Друг хороший у спадкуванні, як інтерфейс стилю для контейнера. Але для мене, як перше кажуть, у 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

І більше, друг [названа область] Річ. *, Щоб надати доступ для конкретних, знаходяться в класі Container через спеціальну іменовану область для друга.

Добре, зупиніть мрію. Але тепер ви знаєте цікаве вживання друга.

В іншому порядку ви також можете виявити цікавим, як відомо всім класам, дружнім із собою. Іншими словами, екземпляр класу може викликати всіх
членів іншого екземпляра з тим самим іменем без обмежень:

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

0

Проста логіка: «У мене є подруга Джейн. Те, що ми подружилися вчора, не означає, що всі її друзі є моїми '.

Мені ще потрібно схвалити ці індивідуальні дружні стосунки, і рівень довіри буде відповідним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.