Предположим, что есть два класса A и B:
class A {};
class B {};
Чем отличаются два приведенных ниже примера?
Пример 1:
class C : public A, public B {};
Пример 2:
class C
{
//private
friend class A;
friend class B;
}
Предположим, что есть два класса A и B:
class A {};
class B {};
Чем отличаются два приведенных ниже примера?
Пример 1:
class C : public A, public B {};
Пример 2:
class C
{
//private
friend class A;
friend class B;
}
friend
может касаться private
частей (каламбур только слегка преднамеренно! ;) ) того, с чем он дружит, но ничто из A
и B
не является частью C
- это просто означает, что "A
и B
могут касаться личных битов C
"). Все "меньше", чем private
, конечно, также доступно для A
и B
, поэтому, если C
состоит из protected
или public
членов, это также будет доступно.
Когда вы наследуете, A
и B
становятся частью C
. Любые private
разделы A
и B
недоступны для C
. В номенклатуре «является-а» и «имеет-а» C
теперь является A
и является B
— другими словами, он унаследован от A
, поэтому он «ведет себя как A
с точки зрения интерфейса.
C
имеет не только A
и B
, но и является и A
, и B
.
- person juanchopanza; 06.08.2013
friendship
дает доступ ко всем битам, общедоступным и закрытым (если бы не было protected
, не стоило бы говорить об этом).
- person juanchopanza; 06.08.2013
Есть несколько больших различий. Наследство и дружба очень разные.
При дружбе класс C НЕ является экземпляром класса A или класса B. Следовательно, если у вас есть такая функция, как:
void processMyClass(A* a);
вы не можете передать ему экземпляр C, тогда как, если C является подклассом A (публично), он ЯВЛЯЕТСЯ экземпляром A.
С помощью дружбы классы A и B могут касаться всех частных данных и функций членов C. С помощью наследования класс C может касаться общедоступных и защищенных членов A и B.
Дружба не передается по наследству. Это означает, например:
class D : public C
{
private:
void foo() {
// A and B cannot call this function
}
}
В контексте, который вы используете, чтобы ответить на ваш вопрос в меру своих возможностей, друзья просто позволяют вашим классам совместно использовать защищенные/частные данные, в то время как наследование будет делать то же самое, за исключением того, что будут более глубокие отношения, где классы являются одним в том же (например, с литьем).
Хотя ответы, которые вы получили, достаточно точны, я не думаю, что они действительно полны. В частности, хотя они и объясняют какую разницу между дружбой и наследованием, в них мало что говорится о том, что и когда следует использовать или как эта разница влияет на ваш код.
Основное использование наследования (в C++) заключается в определении интерфейса в базовом классе и реализации этого интерфейса в каждом из ряда производных классов. Части интерфейса, которые производный класс должен реализовать, обычно обозначаются чисто виртуальными функциями в базовом классе.
Основное использование friend
ship в C++ — определение чего-то, что является частью интерфейса, но по синтаксическим причинам не может быть функцией-членом. Одним чрезвычайно распространенным примером является оператор вставки или извлечения потока. Чтобы реализовать их как функции-члены, они должны быть членами класса потока. Поскольку мы не хотим постоянно изменять класс потока, вместо этого они являются бесплатными функциями, которые принимают ссылку на поток в качестве левого параметра и ссылку на (возможно, константный) объект того типа, который они вставляют/извлекают в качестве своего левого параметра. правый операнд.
Они не должны обязательно быть friend
s класса — они могут быть написаны так, чтобы использовать только открытый интерфейс класса. Однако если вы это сделаете, это, как правило, означает, что класс выставляет в своем публичном интерфейсе больше, чем необходимо в противном случае. Интерфейс больше не минимален, что указывает на проблемный дизайн.
Однако одно замечание: вы можете определить дружественную функцию внутри определения класса:
class Foo {
// ...
friend std::ostream &operator<<(std::ostream &os, Foo const &f) {
// ...
}
};
Поначалу это может показаться странным (да и синтаксически так оно и есть). Несмотря на то, что она определена внутри определения класса, friend
означает, что это не функция-член. По крайней мере, на мой взгляд, это довольно точно отражает ситуацию: концептуально это часть класса. Он имеет доступ к закрытым членам, как и любой другой член класса. Тот факт, что это бесплатная функция, а не функция-член, является чисто артефактом реализации, который по сути не имеет ничего общего с дизайном кода.
Это также указывает на другое различие между friend
ship и наследованием: при наследовании вы обычно имеете дело в основном с функциями-членами. Каждая функция-член по-прежнему получает указатель this
, поэтому каждая функция-член напрямую связана с конкретным экземпляром класса. Да, вы можете определить его так, чтобы он также получал (указатель или ссылку на) другой экземпляр класса, если это необходимо, но он всегда получает this
независимо от этого. Друг (функция или класс) этого не понимает — объявление friend
просто означает, что имена, закрытые для этого другого класса, видны friend
. Чтобы получить доступ к фактическому экземпляру этого класса, вам обычно нужно передать его в качестве параметра или что-то в этом роде.
Наконец, я отмечу, что предыдущий вид игнорирует возможности частного или защищенного наследования. Частное наследование обычно означает, что производный класс реализуется в терминах базового класса. Это может быть удобно, если (например) производный класс подобен базовому классу, но не связан с ним в дизайне, т. е. вы не утверждаете, что экземпляр производного класса может использоваться где угодно. нужен был базовый класс. Использование им базового класса является деталью реализации, о которой остальному миру не нужно знать или заботиться.
Защищенное наследование в значительной степени является ошибкой. Это разрешено, потому что это согласуется с членами public
, private
и protected
(что имеет смысл), но для наследования protected просто не делает ничего полезного.