web-dev-qa-db-fra.com

Où utiliseriez-vous une fonction ami par rapport à une fonction membre statique?

Nous faisons en sorte qu'une fonction non-membre devienne l'ami d'une classe lorsque nous voulons qu'elle accède aux membres privés de cette classe. Cela lui donne les mêmes droits d'accès qu'une fonction membre statique aurait. Les deux alternatives vous donneraient une fonction qui n'est associée à aucune instance de cette classe.

Quand devons-nous utiliser une fonction ami? Quand devons-nous utiliser une fonction statique? Si les deux sont des options viables pour résoudre un problème, comment pouvons-nous évaluer leur pertinence? Y at-il un qui devrait être préféré par défaut?

Par exemple, lors de l'implémentation d'une fabrique qui crée des instances de la classe foo qui ne comporte qu'un constructeur privé, cette fonction de fabrique devrait-elle être un membre statique de foo (vous appelleriez foo::create()) ou une fonction amie (vous appelleriez create_foo())?

58
Swapna

La Section 11.5 "Le langage de programmation C++" de Bjarne Stroustrup indique que les fonctions de membre ordinaires obtiennent 3 choses:

  1. accès aux internes de la classe
  2. sont dans le cadre de la classe
  3. doit être invoqué sur une instance

friends obtenir seulement 1.

static fonctions valent 1 et 2.

63
pm100

La question semble aborder la situation dans laquelle le programmeur doit introduire une fonction qui pas ne fonctionne sur aucune instance d'une classe. (d'où la possibilité de choisir une fonction membre static). Par conséquent, je limiterai cette réponse à la situation de conception suivante, où le choix est entre une fonction statique f() et une fonction libre ami 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
}

Sans rien connaître à l'avance sur la sémantique de f() et A (j'y reviendrai plus tard), ce scénario limité a une réponse simple: - la fonction static est préférable. Je vois deux raisons à cela.


ALGORITHMES GÉNÉRIQUES:

La raison principale est qu'un modèle tel que celui-ci peut être écrit:

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

Si nous avions deux classes ou plus ayant une fonction staticf() sur leur interface, cela nous permettrait d'écrire une seule fonction qui appelle f() de manière générique sur une telle classe.

Il n'y a aucun moyen d'écrire une fonction générique équivalente si nous faisons de f() une fonction libre et non membre. Bien qu'il soit vrai que nous puissions mettre f() dans un espace de noms, afin que la syntaxe N::f() puisse être utilisée pour imiter la syntaxe A::f(), il serait toujours impossible d'écrire une fonction modèle telle que g<>() ci-dessus, car les noms des espaces de noms ne sont pas des arguments de modèle valides. .

DÉCLARATIONS REDONDANTES:

La deuxième raison est que si nous mettions la fonction libre f() dans un espace de noms, nous ne serions pas autorisés à insérer sa définition directement dans la définition de classe sans introduire aucune autre déclaration pour f():

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

Afin de corriger ce qui précède, nous voudrions faire précéder la définition de la classe A par la déclaration suivante:

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

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

Ceci, cependant, contredit notre intention de faire en sorte que f() soit déclaré et défini à un seul endroit.

De plus, si nous voulions déclarer et définir f() séparément tout en conservant f() dans un espace de noms, nous devrions toujours introduire une déclaration pour f() avant la définition de classe pour A: faute de quoi le compilateur se plaindrait du fait que f() devait être déclaré dans l'espace de noms N avant que le nom N::f puisse être utilisé légalement.

Ainsi, nous aurions maintenant f() mentionné dans trois endroits distincts plutôt que deux (déclaration et définition):

  • Déclaration dans l'espace de noms N avant la définition de A;
  • La déclaration friend dans la définition de A;
  • La définition de f() dans le namespace N.

La raison pour laquelle la déclaration et la définition de f() dans N ne peut pas être jointe (en général) est que f() est censé accéder aux éléments internes de A et que, par conséquent, la définition de A doit être vue lorsque f() est défini. Cependant, comme indiqué précédemment, la déclaration de f() à l'intérieur de N doit être vue avant que la déclaration friend correspondante à l'intérieur de A soit faite. Cela nous oblige effectivement à scinder la déclaration et la définition de f().


CONSIDERATIONS SEMANTIQUES:

Bien que les deux points ci-dessus soient universellement valables, il y a des raisons pour lesquelles on pourrait préférer déclarer f() en tant que static plutôt que d'en faire un friend de A ou vice-versa, qui sont guidés par l'univers du discours.

Pour clarifier, il est important de souligner le fait qu’une fonction membre d’une classe, qu’elle soit static ou non -static, fait logiquement partie de : classe. Il contribue à sa définition et en fournit donc une caractérisation conceptuelle.

D'autre part, une fonction friend, bien que l'accès aux membres internes de la classe dont elle est amie, reste un algorithme logiquement externe à la définition de la classe.

Une fonction peut être friend de plusieurs classes, mais elle ne peut appartenir qu'à une seule .

Ainsi, dans un domaine d'application particulier, le concepteur voudra peut-être tenir compte de la sémantique de la fonction et de la classe lorsqu'il décidera de transformer le premier en friend ou un membre de celle-ci (ceci s'applique non seulement aux fonctions static, mais également aux fonctions non -static, où d'autres contraintes de langage peuvent intervenir).

La fonction contribue-t-elle logiquement à caractériser une classe et/ou son comportement, ou s'agit-il plutôt d'un algorithme externe? Il est impossible de répondre à cette question sans connaître le domaine d'application particulier.


TASTE:

Je crois que tout argument autre que celui que nous venons de donner découle purement d'une approche question de goût: les approches friend libre et membre static permettent en fait d'indiquer clairement en quoi consiste l'interface d'une classe. un seul endroit (la définition de la classe), donc leur conception est équivalente (modulo les observations ci-dessus, bien sûr).

Les différences restantes sont stylistiques: si nous voulons écrire le mot clé static ou le mot clé friend lors de la déclaration d'une fonction et si nous voulons écrire le qualificatif de portée de la classe A:: lors de la définition de la classe plutôt que le N:: qualificateur de portée d'espace de noms. Par conséquent, je ne ferai pas de commentaire supplémentaire à ce sujet.

41
Andy Prowl

La différence exprime clairement l'intention de la relation entre la classe et la fonction. 

Vous utilisez friend lorsque vous souhaitez indiquer intentionnellement un couplage fort et une relation spéciale entre deux classes non liées ou entre une classe et une fonction.

Vous utilisez la fonction membre static lorsque la fonction fait logiquement partie de la classe à laquelle elle appartient.

10
Alok Save

Les fonctions statiques sont utilisées lorsque vous souhaitez une fonction identique pour toutes les instances d'une classe. Ces fonctions n'ont pas accès à "ce" pointeur et ne peuvent donc accéder à aucun champ non statique. Ils sont souvent utilisés lorsque vous voulez une fonction qui peut être utilisée sans instancier la classe. 

Les fonctions d'ami sont des fonctions qui ne font pas partie de la classe et que vous souhaitez leur donner accès à des membres privés de votre classe.

Et ceci (statique ou ami) ne consiste pas à utiliser l’un contre l’autre car ils ne sont pas opposés.

4
synepis

Les fonctions ami (et les classes) peuvent accéder aux membres privés et protégés de votre classe. Il y a rarement de bonnes raisons d'utiliser une fonction ou une classe d'amis. Evitez-les en général.

Les fonctions statiques ne peuvent accéder qu'aux données statiques (c'est-à-dire aux données de portée de classe). Ils peuvent être appelés sans créer d'instance de votre classe. Les fonctions statiques conviennent parfaitement lorsque vous souhaitez que toutes les instances de votre classe se comportent de la même manière. Vous pouvez les utiliser:

  • comme fonctions de rappel
  • manipuler des membres de classe
  • pour récupérer des données constantes que vous ne voulez pas énumérer dans votre fichier d'en-tête
  • 4
    thebretness

    La norme exige que operator = () [] et -> doivent être membres, et spécifiques à la classe
    Les opérateurs new, new [], delete et delete [] doivent être des membres statiques. Si la situation
    survient lorsque nous n'avons pas besoin de l'objet de la classe pour appeler une fonction, puis
    la fonction static. Pour toutes les autres fonctions:
    si une fonction nécessite les opérateurs = () [] et -> pour le flux d'E/S,
    ou s'il a besoin de conversions de type sur son argument le plus à gauche, ou s'il peut être implémenté en utilisant uniquement l'interface publique de la classe, faites-le non membre (et ami si nécessaire dans les deux premiers cas)
    si elle doit se comporter virtuellement,
    ajouter une fonction de membre virtuel pour fournir le comportement virtuel
    et mettre en œuvre en termes de
    autre
    en faire un membre.

    2
    Jagannath
    • Une des raisons de préférer un ami à un membre statique est lorsque la fonction doit être écrite dans Assembly (ou dans une autre langue).

      Par exemple, nous pouvons toujours avoir une fonction amie "C" externe déclarée dans notre fichier .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);
      };
      

      Et plus tard défini dans Assembly:

                      .global ContextSwitch
      
      ContextSwitch:  // ...
                      retq
      

      Techniquement parlant, nous pourrions utiliser une fonction membre statique pour le faire, mais sa définition dans Assembly ne sera pas facile en raison du nom mangling ( http://en.wikipedia.org/wiki/Name_mangling )

    • Une autre situation est lorsque vous devez surcharger les opérateurs. Les opérateurs surchargés ne peuvent être créés qu’avec des amis ou des membres non statiques. Si le premier argument de l'opérateur n'est pas une instance de la même classe, le membre non statique ne fonctionnerait pas non plus; ami serait la seule option:

      class Matrix
      {
          friend Matrix operator * (double scaleFactor, Matrix & m);
          // We can't use static member or non-static member to do this
      };
      
    2
    nav

    La fonction statique ne peut accéder qu'aux membres de one class. La fonction Friend a accès à plusieurs classes, comme expliqué par le code suivant:

    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 () peut accéder aux données des classes A et B.

    2
    tp1

    Une fonction ami ne peut pas être héritée, alors qu'une fonction statique peut l'être. Ainsi, lorsqu'un objectif peut être atteint à la fois avec une fonction statique et une fonction d'amis, pensez-y que vous souhaitiez en hériter ou non.

    1
    UserXYZ

    Vous utiliserez une fonction statique si la fonction n'a pas besoin de lire ou de modifier l'état d'une instance spécifique de la classe (ce qui signifie que vous n'avez pas besoin de modifier l'objet en mémoire), ou si vous devez utiliser un pointeur de fonction pour une fonction membre d'une classe. Dans ce deuxième cas, si vous devez modifier l'état de l'objet résident, vous devez transmettre this et utiliser la copie locale. Dans le premier cas, une telle situation peut se produire lorsque la logique pour effectuer une tâche donnée ne dépend pas de l'état d'un objet, alors que votre groupement logique et votre encapsulation voudraient qu'il soit membre d'une classe spécifique.

    Vous utilisez une fonction ou une classe d'amis lorsque vous avez créé du code qui n'est pas un membre de votre classe et ne devrait pas l'être, mais qui a un but légitime pour contourner les mécanismes d'encapsulation privés/protégés. L’un des objectifs de cette méthode est peut-être que deux classes ont besoin de données communes, mais coder deux fois la logique serait mauvais. Vraiment, je n'ai utilisé cette fonctionnalité que dans peut-être 1% des classes que j'ai jamais codées. C'est rarement nécessaire.

    1
    San Jacinto

    Les fonctions ami peuvent accéder aux membres privés et protégés d’autres classes . Cela signifie qu’elles peuvent être utilisées pour accéder à toutes les données, qu’elles soient privées ou publiques . .

    Ces méthodes sont rendues statiques, appelées tant de fois que déclarer un emplacement différent à l'intérieur de chaque objet devient trop coûteux (en termes de mémoire) . Cela peut être clarifié à l'aide de l'exemple: le nom de la classe est fact et son membre de données est n (ce qui représente un entier dont la factorielle est un problème) dans ce cas, déclarer find_factorial () comme statique serait une bonne décision!

    Elles sont utilisées en tant que fonctions de rappelpour manipuler des membres de classepour récupérer des données constantes que vous ne voulez pas énumérer dans votre fichier d'en-tête

    Maintenant, nous sommes clairs avec les questions suivantes.

    Quand une fonction ami est utilisée? Quand une fonction statique est utilisée? 

    Maintenant, si les deux solutions sont viables pour résoudre un problème, Nous pouvons évaluer leur pertinence en termes d’accessibilité (accessibilité des données privées) et d’efficacité de la mémoire . nous avons besoin d’une meilleure gestion de la mémoire et nous sommes parfois préoccupés par la portée des données.

    Par exemple: Foo :: create () sera préféré à create_foo () lorsque nous devons appeler la méthode create () après chaque petite instance de temps et que nous ne sommes pas intéressés par l'étendue des données (données privées).

    Et si nous sommes intéressés à obtenir les informations privées de plusieurs classes, create_foo () sera préféré à foo :: create ().

    J'espère que cela vous aiderait !!

    0
    newbieprog

    Une fonction statique est une fonction qui n'a pas accès à this.

    Une fonction ami est une fonction qui peut accéder aux membres privés de la classe.

    0
    tzenes

    La fonction statique peut être utilisée de différentes manières.

    Par exemple, en tant que fonction d'usine simple:

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

    Ceci est un exemple très simplifié mais j'espère que cela explique ce que je voulais dire.

    Ou en tant que fonction de fil travaillant avec votre classe:

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

    Friend est une histoire complètement différente et leur utilisation est exactement celle décrite par thebretness

    0
    lollinus