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

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

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

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

15 ответов

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

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

friends получить только 1.

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

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

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

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

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

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

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

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

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

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

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

    Статическая функция может получить доступ только к членам одного класса. Функция 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.

    • Одна из причин предпочитать друга статическому члену - это когда функцию нужно писать на ассемблере (или другом языке).

      Например, у нас всегда может быть внешняя функция друга "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
      };
      

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

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

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

    Статическая функция - это функция, которая не имеет доступа к this,

    Функция Friend - это функция, которая может получить доступ к закрытым членам класса.

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

    Эти методы становятся статичными, которые вызываются так много раз, что объявляют разные места внутри каждого объекта, потому что они становятся слишком дорогими (с точки зрения памяти). Это можно прояснить с помощью примера: пусть имя класса является фактом, а его член данных равен n(что представляет собой целое число, факториал которого имеет значение), тогда в этом случае объявление find_factorial() как статического было бы мудрым решением!!

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

    Теперь мы ясно со следующими вопросами..

    Когда используется функция друга? Когда используется статическая функция?

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

    Например: foo::create() будет предпочтительнее, чем create_foo(), когда мы будем вызывать метод create () после каждого небольшого момента времени, и нас не интересует объем данных (личные данные)

    И если нам интересно получить конфиденциальную информацию более чем одного класса (классов), то create_foo() будет предпочтительнее, чем foo::create().

    Я надеюсь, что это поможет вам!

    Статическая функция может использоваться по-разному.

    Например, как простая фабричная функция:

      class Abstract {
      private:
        // no explicit construction allowed
        Abstract(); 
        ~Abstract();
    
       public:
         static Abstract* Construct() { return new Abstract; }
         static void Destroy(Abstract* a) { delete a; }
       };
    
       ...
       A* a_instance = A::Conctruct();
       ...
       A::Destroy(a_instance);
    

    Это очень упрощенный пример, но я надеюсь, что он объяснит, что я имел в виду.

    Или как функция потока, работающая с Вашим классом:

     class A {
    
     public:
        static void worker(void* p) {
                A* a = dynamic_cast<A*>(p);
                do something wit a;
        }   
     } 
    
     A a_instance;
     pthread_start(&thread_id, &A::worker, &a_instance);
     .... 
    

    У друга совершенно другая история, и их использование в точности соответствует описанию

    1. Статические члены данных всегда разделяют память.
    2. только статическая функция может использовать статические данные-члены.
    3. Статическая функция-член может быть вызвана с именем класса.
    4. Они должны быть определены вне класса, когда мы создаем объект статического члена или функции-члена в классе. Это автоматически инициализирует значение.
    5. Он всегда использовал ключевое слово static.
    6. Статические члены могут делиться всеми объектами.
    7. Тип и область данных-членов и функции-члена находятся вне класса.
    8. Статическая переменная-член должна быть определена вне класса.

    Вот что я думаю:

    Функция Friend - когда вам нужен доступ к другому члену класса, но классы не связаны. Статическая функция - когда вам не нужен доступ к указателю "this". Но у меня есть ощущение, что это еще не все....

    Другие вопросы по тегам