it-swarm.com.ru

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

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

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

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

59
Swapna

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

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

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

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

67
pm100

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

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

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

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


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

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

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

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

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

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

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


ВКУС:

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

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

39
Andy Prowl

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

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

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

10
Alok Save

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

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

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

4
synepis

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

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

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

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

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

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

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

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

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

    1
    UserXYZ

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

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

    1
    San Jacinto

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

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

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

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

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

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

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

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

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

    0
    newbieprog

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

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

    0
    tzenes

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

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

      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);
     .... 
    

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

    0
    lollinus