Где бы вы использовали функцию друга вместо статической функции-члена? - PullRequest
59 голосов
/ 23 февраля 2010

Мы делаем функцию, не являющуюся членом, другом класса, когда хотим, чтобы он имел доступ к закрытым членам этого класса. Это дает ему те же права доступа, что и статическая функция-член. Обе альтернативы дадут вам функцию, которая не связана ни с одним экземпляром этого класса.

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

Например, при реализации фабрики, которая создает экземпляры класса foo, который имеет только закрытый конструктор, должна ли эта фабричная функция быть статическим членом foo (вы бы назвали foo::create()) или это функция друга (вы бы назвали create_foo())?

Ответы [ 15 ]

68 голосов
/ 23 февраля 2010

Раздел 11.5 «Язык программирования C ++» Бьярна Страуструпа утверждает, что обычные функции-члены получают 3 вещи:

  1. доступ к внутренностям класса
  2. входят в сферу деятельности класса
  3. должен быть вызван для экземпляра

friend с получить только 1.

static функции получают 1 и 2.

39 голосов
/ 14 февраля 2013

Вопрос, кажется, касается ситуации, когда программисту нужно ввести функцию, которая не работает на любом экземпляре класса (отсюда и возможность выбора static функция-член). Поэтому я ограничу этот ответ следующей конструкторской ситуацией, когда выбор между статической функцией f() и функцией без друзей f():

struct A
{
    static void f();     // Better this...
private:
    friend void f();  // ...or this?
    static int x;
};

int A::x = 0;

void A::f() // Defines static function
{
    cout << x;
}

void f() // Defines friend free function
{
    cout << A::x;
}

int main()
{
    A::f(); // Invokes static function
    f();    // Invokes friend free function
}

Не зная заранее о семантике из f() и A (я вернусь к этому позже), у этого ограниченного сценария простой ответ: the static функция предпочтительна . Я вижу две причины для этого.


ОБЩИЕ АЛГОРИТМЫ:

Основная причина в том, что такой шаблон может быть написан следующим образом:

template<typename T> void g() { T::f(); }

Если бы у нас было два или более классов, имеющих на своем интерфейсе функцию static f(), это позволило бы нам написать одну единственную функцию, которая в общем случае вызывает f() для любого такого класса.

Невозможно написать эквивалентную обобщенную функцию, если мы сделаем f() бесплатной функцией, не являющейся членом. Хотя это правда, что мы можем поместить f() в пространство имен, чтобы синтаксис N::f() мог использоваться для имитации синтаксиса A::f(), все равно было бы невозможно написать функцию шаблона, такую ​​как g<>() выше, потому что имена пространств имен не являются допустимыми аргументами шаблона.

ИЗБЫТОЧНЫЕ ДЕКЛАРАЦИИ:

Вторая причина в том, что если бы мы поместили свободную функцию f() в пространство имен, нам бы не разрешили встроить ее определение непосредственно в определение класса, не вводя никакого другого объявления для f()

struct A
{
    static void f() { cout << x; } // OK
private:
    friend void N::f() { cout << x; } // ERROR 
    static int x;
};

Чтобы исправить вышесказанное, мы должны предшествовать определению класса A следующим объявлением:

namespace N
{
    void f(); // Declaration of f() inside namespace N
}

struct A
{
    ...
private:
    friend void N::f() { cout << x; } // OK
    ...
};

Это, однако, противоречит нашему намерению объявить и определить f() только в одном месте.

Более того, если бы мы хотели объявить и определить f() отдельно, сохраняя f() в пространстве имен, нам все равно пришлось бы ввести объявление для f() перед определением класса для A: не сделать этого приведет к тому, что компилятор пожалуется на тот факт, что f() должен быть объявлен внутри пространства имен N, прежде чем имя N::f может быть использовано легально.

Таким образом, теперь у нас будет f(), упомянутое в трех отдельных местах, а не только в двух (объявление и определение):

  • Объявление внутри пространства имен N до определения A;
  • Объявление friend внутри определения A;
  • Определение f() внутри пространства имен N.

Причина, по которой объявление и определение f() внутри N не могут быть объединены (в общем), заключается в том, что f() должен иметь доступ к внутренним частям A и, следовательно, к определению A должно быть видно, когда определено f(). Тем не менее, как уже было сказано, объявление f() внутри N должно быть видно до того, как будет сделано соответствующее объявление friend внутри A. Это фактически заставляет нас разделить декларацию и определение f().


СЕМАНТИЧЕСКИЕ СООБРАЖЕНИЯ:

В то время как вышеупомянутые два пункта универсально действительны, есть причины, по которым можно предпочесть объявить f() как static вместо того, чтобы сделать его friend из A или наоборот, что обусловлено вселенной дискурса.

Чтобы уточнить, важно подчеркнуть тот факт, что функция-член класса, будь то static или не static, логически является частью этого класса. Это способствует его определению и, таким образом, дает концептуальную характеристику этого.

С другой стороны, функция friend, несмотря на то, что ей предоставлен доступ к внутренним членам класса, другу которого она является, все еще остается алгоритмом, логически внешним для определения класс.

Функция может быть friend из более чем одного класса, но может быть членом только одного .

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

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


ВКУС:

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

Остальные различия являются стилистическими: хотим ли мы написать ключевое слово static или ключевое слово friend при объявлении функции, и хотим ли мы написать квалификатор A:: при определении класса вместо определения класса N:: Спецификатор области имен пространства имен. Таким образом, я не буду комментировать дальше.

10 голосов
/ 11 февраля 2013

Разница четко выражает намерение отношений между классом и функцией.

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

Вы используете static функция-член, когда функция логически является частью класса , членом которого она является.

4 голосов
/ 23 февраля 2010

Функции друзей (и классы) могут получить доступ к закрытым и защищенным членам вашего класса. Там редко хороший случай для использования функции друга или класса. Избегайте их вообще.

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

в качестве функций обратного вызова для манипулирования членами в классе для извлечения постоянных данных, которые вы не хотите перечислять в заголовочном файле
4 голосов
/ 23 февраля 2010

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

Функции друзей - это функции, которых нет в классе, и вы хотите предоставить им доступ к закрытым членам вашего класса.

И это (статично против друга) не является вопросом использования одного против другого, так как они не противоположны.

2 голосов
/ 11 февраля 2013
  • Одна из причин, по которой друзья предпочитают статические элементы, - это когда необходимо написать функцию на ассемблере (или на другом языке).

    Например, у нас всегда может быть внешняя функция друга "C", объявленная в нашем файле .cpp

    class Thread;
    extern "C" int ContextSwitch(Thread & a, Thread & b);
    
    class Thread
    {
    public:
        friend int ContextSwitch(Thread & a, Thread & b);
        static int StContextSwitch(Thread & a, Thread & b);
    };
    

    и позже определено в сборке:

                    .global ContextSwitch
    
    ContextSwitch:  // ...
                    retq
    

    Технически говоря, мы могли бы использовать статическую функцию-член для этого, но определить ее в сборке будет нелегко из-за искажения имени (http://en.wikipedia.org/wiki/Name_mangling)

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

    class Matrix
    {
        friend Matrix operator * (double scaleFactor, Matrix & m);
        // We can't use static member or non-static member to do this
    };
    
2 голосов
/ 10 февраля 2013

Статическая функция может получить доступ только к членам класса one . Функция Friend имеет доступ к нескольким классам, как объясняется следующим кодом:

class B;
class A { int a; friend void f(A &a, B &b); };
class B { int b; friend void f(A &a, B &b); };
void f(A &a, B &b) { std::cout << a.a << b.b; }

f () может обращаться к данным как класса A, так и класса B.

2 голосов
/ 23 февраля 2010

Стандарт требует, чтобы операторы = () [] и -> были членами, и для класса
операторы new, new [], delete и delete [] должны быть статическими членами. Если ситуация
возникает там, где нам не нужен объект класса для вызова функции, а затем сделать
функция статическая. Для всех остальных функций:
если функция требует операторов = () [] и -> для потокового ввода / вывода,
или если ему нужны преобразования типов в крайнем левом аргументе, или если это может быть реализовано с использованием только открытого интерфейса класса, сделать его незарегистрированным (и другом при необходимости в первых двух случаях)
если нужно вести себя виртуально,
добавить функцию виртуального члена для обеспечения виртуального поведения
и реализовать с точки зрения этого
еще
сделать его членом.

1 голос
/ 17 октября 2016

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

1 голос
/ 23 февраля 2010

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

Вы используете функцию или класс друга, когда создали код, который не является членом вашего класса и не должен быть членом вашего класса, но имеет законную цель для обхода закрытых / защищенных механизмов инкапсуляции. Одна из целей этого может заключаться в том, что у вас есть два класса, которым нужны некоторые общие данные, но дважды кодировать логику было бы плохо. На самом деле, я использовал эту функциональность только в 1% классов, которые я когда-либо кодировал. Это редко требуется.

...