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()
)?
La Section 11.5 "Le langage de programmation C++" de Bjarne Stroustrup indique que les fonctions de membre ordinaires obtiennent 3 choses:
friend
s obtenir seulement 1.
static
fonctions valent 1 et 2.
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 static
f()
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):
N
avant la définition de A
;friend
dans la définition de A
;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.
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.
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.
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:
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.
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
};
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.
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.
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.
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 !!
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.
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