Quando dovresti usare "amico" in C ++?


354

Ho letto le domande frequenti sul C ++ ed ero curioso della frienddichiarazione. Personalmente non l'ho mai usato, tuttavia sono interessato a esplorare la lingua.

Qual è un buon esempio di utilizzo friend?


Leggendo le FAQ un po 'di più, mi piace l'idea che l' << >>operatore sovraccarichi e aggiunga come amico di quelle classi. Tuttavia non sono sicuro di come ciò non rompa l'incapsulamento. Quando queste eccezioni possono rimanere all'interno della rigidità che è OOP?


5
Mentre sono d'accordo con la risposta che una classe di amici non è necessariamente una cosa negativa, tendo a trattarla come un piccolo codice. Spesso, sebbene non sempre, indica che è necessario riconsiderare la gerarchia di classi.
Mawg dice di ripristinare Monica il

1
Utilizzeresti una classe di amici in cui esiste già un accoppiamento stretto. È per questo che è fatto. Ad esempio, una tabella di database e i suoi indici sono strettamente accoppiati. Quando una tabella cambia, è necessario aggiornare tutti i suoi indici. Pertanto, la classe DBIndex dichiarerebbe DBTable come amico in modo che DBTable possa accedere direttamente agli interni dell'indice. Ma non ci sarebbe un'interfaccia pubblica per DBIndex; non ha nemmeno senso leggere un indice.
Shawnhcorey,

I "puristi" di OOP con poca esperienza pratica sostengono che un amico viola i principi di OOP, perché una classe dovrebbe essere l'unica a mantenere il proprio stato privato. Questo va bene, fino a quando non si verifica una situazione comune in cui due classi devono mantenere uno stato privato condiviso.
Kaalus l'

Risposte:


335

In primo luogo (IMO) non ascoltare le persone che dicono che friendnon è utile. È utile. In molte situazioni avrai oggetti con dati o funzionalità che non sono destinati a essere disponibili pubblicamente. Ciò è particolarmente vero per basi di codice di grandi dimensioni con molti autori che possono avere solo superficialmente familiarità con aree diverse.

Ci sono alternative allo identificatore di amici, ma spesso sono ingombranti (classi concrete a livello di cpp / typedef mascherati) o non infallibili (commenti o convenzioni sui nomi delle funzioni).

Sulla risposta;

L' friendidentificatore consente alla classe designata di accedere a dati o funzionalità protette all'interno della classe che rilascia la dichiarazione di amicizia. Ad esempio, nel codice seguente chiunque può chiedere a un figlio il proprio nome, ma solo la madre e il figlio possono cambiare il nome.

Puoi prendere questo semplice esempio ulteriormente considerando una classe più complessa come una finestra. Molto probabilmente una finestra avrà molti elementi funzione / dati che non dovrebbero essere accessibili pubblicamente, ma SONO necessari per una classe correlata come WindowManager.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
Come nota aggiuntiva, le Domande frequenti su C ++ menzionano il friend miglioramento dell'incapsulamento. friendgarantisce un accesso selettivo ai membri, proprio come protectedfa. Qualsiasi controllo approfondito è meglio che garantire l'accesso del pubblico. Altre lingue definiscono anche i meccanismi di accesso selettivo, considera quelli di C # internal. La maggior parte delle critiche negative sull'uso di friendè legato all'accoppiamento più stretto, che è generalmente visto come una cosa negativa. Tuttavia, in alcuni casi, un accoppiamento più stretto è esattamente quello che vuoi e friendti dà quel potere.
André Caron,

5
Potresti dire di più su (classi concrete di livello cpp) e (typedef mascherati), Andrew ?
OmarOthman,

18
Questa risposta sembra essere più focalizzata sulla spiegazione di ciò che friendè piuttosto che fornire un esempio motivante . L'esempio Window / WindowManager è migliore dell'esempio mostrato, ma troppo vago. Anche questa risposta non riguarda la parte incapsulata della domanda.
bames53,

4
Quindi "amico" esiste davvero perché C ++ non ha idea di un pacchetto in cui tutti i membri possano condividere i dettagli dell'implementazione? Sarei davvero interessato a un esempio del mondo reale.
weberc2,

1
Penso che l'esempio Madre / Figlio sia sfortunato. Questi nomi sono adatti per le istanze, ma non per le classi. (Il problema mostra se abbiamo 2 madri e ognuna ha il proprio figlio).
Jo So,

162

Al lavoro utilizziamo gli amici per testare ampiamente il codice . Significa che siamo in grado di fornire un corretto incapsulamento e nascondere le informazioni per il codice dell'applicazione principale. Ma possiamo anche avere un codice di test separato che utilizza gli amici per ispezionare lo stato interno e i dati per i test.

Basti dire che non userei la parola chiave friend come componente essenziale del tuo design.


È esattamente per questo che lo uso. Quello o semplicemente imposta le variabili membro su protette. È solo un peccato che non funzioni con C ++ / CLI :-(
Jon Cage,

12
Personalmente lo scoraggerei. In genere si sta testando un'interfaccia, ovvero una serie di input fornisce la serie prevista di output. Perché è necessario ispezionare i dati interni?
Graeme,

55
@Graeme: perché un buon piano di test include sia test su scatola bianca che su scatola nera.
Ben Voigt,

1
Tendo ad essere d'accordo con @Graeme, come spiegato perfettamente in questa risposta .
Alexis Leclerc,

2
@Graeme potrebbe non essere direttamente dati interni. Potrei essere un metodo che esegue un'operazione o un'attività specifica su quei dati in cui quel metodo è privato per la classe e non dovrebbe essere accessibile pubblicamente mentre qualche altro oggetto potrebbe aver bisogno di alimentare o seminare il metodo protetto di quella classe con i propri dati.
Francis Cugler,

93

La friendparola chiave ha molti usi utili. Ecco i due usi immediatamente visibili per me:

Definizione dell'amico

La definizione di amico consente di definire una funzione nell'ambito della classe, ma la funzione non sarà definita come una funzione membro, ma come una funzione libera dello spazio dei nomi racchiuso e non sarà visibile normalmente tranne la ricerca dipendente dall'argomento. Ciò lo rende particolarmente utile per il sovraccarico dell'operatore:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Classe di base CRTP privata

A volte, trovi la necessità che un criterio abbia bisogno dell'accesso alla classe derivata:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

Troverai un esempio non inventato per questo in questa risposta. Un altro codice che utilizza questo è in questa risposta. La base CRTP lancia questo puntatore, per poter accedere ai campi di dati della classe derivata usando i puntatori di membri di dati.


Ciao, ricevo un errore di sintassi (in xcode 4) quando provo il tuo CRTP. Xcode crede che sto cercando di ereditare un modello di classe. L'errore si verifica al P<C>di template<template<typename> class P> class C : P<C> {};affermando "L'uso del modello di classe C richiede argomenti template". Hai avuto gli stessi problemi o forse conosci una soluzione?
bennedich,

@bennedich a prima vista, sembra il tipo di errore che potresti ottenere con un supporto insufficiente per le funzionalità C ++. Il che è abbastanza comune tra i compilatori. L'uso di FlexibleClassinside FlexibleClassdovrebbe implicitamente riferirsi al proprio tipo.
Yakk - Adam Nevraumont,

@bennedich: le regole per l'uso del nome di un modello di classe all'interno del corpo della classe sono state modificate con C ++ 11. Prova ad abilitare la modalità C ++ 11 nel tuo compilatore.
Ben Voigt,

In Visual Studio 2015 aggiungi questo pubblico: f () {}; f (int_type t): valore (t) {}; Per evitare questo errore del compilatore: errore C2440: '<function-style-cast>': impossibile convertire da 'utils :: f :: int_type' a 'utils :: f' nota: nessun costruttore può accettare il tipo di origine o il costruttore la risoluzione del sovraccarico era ambigua
Damian il

41

@roo : l'incapsulamento non è interrotto qui perché la classe stessa determina chi può accedere ai suoi membri privati. L'incapsulamento verrebbe interrotto solo se ciò potesse essere causato dall'esterno della classe, ad esempio se tu operator <<proclamassi "Sono un amico di classe foo".

friendsostituisce l'uso di public, non l'uso di private!

In realtà, le FAQ C ++ rispondono già a questa domanda .


14
"L'amico sostituisce l'uso del pubblico, non l'uso del privato!", secondo che
Waleed Eissa,

26
@Assaf: sì, ma il FQA è, per la maggior parte a, un sacco di incoerenti rabbia senza senso, senza alcun valore reale. La parte su friendnon fa eccezione. L'unica vera osservazione qui è che C ++ garantisce l'incapsulamento solo in fase di compilazione. E non hai bisogno di più parole per dirlo. Il resto sono bollocks. Quindi, in sintesi: non vale la pena menzionare questa sezione della FQA.
Konrad Rudolph,

12
Gran parte di questo FQA è assolutamente blx :)
rama-jka toti,

1
@Konrad: "L'unica vera osservazione qui è che C ++ garantisce l'incapsulamento solo in fase di compilazione." Qualche lingua lo garantisce in fase di esecuzione? Per quanto ne so, la restituzione di riferimenti a membri privati ​​(e funzioni, per linguaggi che consentono puntatori a funzioni o funzioni come oggetti di prima classe) è consentita in C #, Java, Python e molti altri.
André Caron,

@ André: la JVM e il CLR possono effettivamente garantire questo per quanto ne so. Non so se è sempre fatto, ma è possibile presumibilmente proteggere pacchetti / assiemi da tale intrusione (non l'ho mai fatto da solo).
Konrad Rudolph,

27

L'esempio canonico è quello di sovraccaricare l'operatore <<. Un altro uso comune è quello di consentire un accesso di classe helper o admin ai tuoi interni.

Ecco un paio di linee guida che ho sentito parlare degli amici di C ++. L'ultimo è particolarmente memorabile.

  • I tuoi amici non sono amici di tuo figlio.
  • Gli amici di tuo figlio non sono i tuoi amici.
  • Solo gli amici possono toccare le tue parti private.

" L'esempio canonico è quello di sovraccaricare l'operatore <<. " friendCredo che il canonico di non usare .
curiousguy il

16

modifica: Leggendo la FAQ un po 'più a lungo mi piace l'idea che l'operatore << >> sovraccarichi e aggiunga come amico di quelle classi, tuttavia non sono sicuro di come ciò non rompa l'incapsulamento

Come spezzerebbe l'incapsulamento?

Si interrompe l'incapsulamento quando si consente l' accesso senza restrizioni a un membro di dati. Considera le seguenti classi:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1è ovviamente non incapsulato. Chiunque può leggere e modificare xal suo interno. Non abbiamo modo di applicare alcun tipo di controllo di accesso.

c2è ovviamente incapsulato. Non c'è accesso pubblico a x. Tutto quello che puoi fare è chiamare la foofunzione, che esegue alcune operazioni significative sulla classe .

c3? È meno incapsulato? Permette l'accesso illimitato ax ? Permette l'accesso a funzioni sconosciute?

No. Permette precisamente a una funzione di accedere ai membri privati ​​della classe. Proprio come ha c2fatto. E proprio come c2, l'unica funzione che ha accesso non è "una funzione casuale, sconosciuta", ma "la funzione elencata nella definizione della classe". Proprio come c2, possiamo vedere, semplicemente guardando le definizioni delle classi, un elenco completo di chi ha accesso.

Quindi, come è esattamente questo meno incapsulato? La stessa quantità di codice ha accesso ai membri privati ​​della classe. E tutti coloro che hanno accesso sono elencati nella definizione della classe.

friendnon rompe l'incapsulamento. Ciò rende alcuni programmatori di persone Java a disagio, perché quando dicono "OOP", in realtà significano "Java". Quando dicono "Incapsulamento", non significano "i membri privati ​​devono essere protetti da accessi arbitrari", ma "una classe Java in cui le uniche funzioni in grado di accedere ai membri privati, sono membri della classe", anche se questa è una totale assurdità per diverse ragioni .

Innanzitutto, come già mostrato, è troppo restrittivo. Non c'è motivo per cui i metodi degli amici non dovrebbero fare lo stesso.

Secondo, non è abbastanza restrittivo . Considera una quarta classe:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

Questo, secondo la suddetta mentalità Java, è perfettamente incapsulato. Eppure, consente a chiunque di leggere e modificare assolutamente x . Che senso ha questo senso? (suggerimento: non lo fa)

In conclusione: l'incapsulamento riguarda la capacità di controllare quali funzioni possono accedere ai membri privati. E ' non è su esattamente dove si trovano le definizioni di queste funzioni.


10

Un'altra versione comune dell'esempio di Andrew, il temuto couplet di codice

parent.addChild(child);
child.setParent(parent);

Invece di preoccuparti se entrambe le linee sono sempre fatte insieme e in ordine coerente potresti rendere i metodi privati ​​e avere una funzione di amicizia per imporre coerenza:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

In altre parole, è possibile ridurre le interfacce pubbliche e imporre invarianti che attraversano classi e oggetti nelle funzioni degli amici.


6
Perché qualcuno dovrebbe aver bisogno di un amico per quello? Perché non lasciare che la addChildfunzione membro imposti anche il genitore?
Nawaz,

1
Un esempio migliore sarebbe fare setParentamicizia, poiché non si desidera consentire ai clienti di modificare il genitore poiché lo si gestirà nella categoria addChild/ removeChildfunzioni.
Ylisar,

8

Controlli i diritti di accesso per i membri e le funzioni usando il diritto privato / protetto / pubblico? quindi supponendo che l'idea di ognuno di questi 3 livelli sia chiara, allora dovrebbe essere chiaro che ci manca qualcosa ...

La dichiarazione di un membro / funzione come protetta ad esempio è piuttosto generica. Stai dicendo che questa funzione è fuori dalla portata di tutti (tranne ovviamente per un figlio ereditato). Ma che dire delle eccezioni? ogni sistema di sicurezza ti permette di avere un qualche tipo di "lista bianca" giusto?

Quindi amico ti consente di avere la flessibilità di avere l'isolamento di oggetti solidi come una roccia, ma consente di creare una "scappatoia" per le cose che ritieni siano giustificate.

Immagino che la gente dica che non è necessario perché c'è sempre un design che ne farà a meno. Penso che sia simile alla discussione sulle variabili globali: non dovresti mai usarle, c'è sempre un modo per farne a meno ... ma in realtà, vedi casi in cui finisce per essere il modo (quasi) più elegante. .. Penso che questo sia lo stesso caso con gli amici.

In realtà non fa nulla di buono, oltre a consentire l'accesso a una variabile membro senza utilizzare una funzione di impostazione

bene, non è esattamente il modo di vederlo. L'idea è di controllare chi può accedere a ciò, avere o meno una funzione di impostazione ha poco a che fare con esso.


2
Come sta frienduna scappatoia? Consente ai metodi elencati nella classe di accedere ai suoi membri privati. Non consente ancora l'accesso a codice arbitrario. In quanto tale, non è diverso da una funzione di membro pubblico.
jalf,

amico è il più vicino possibile ad accedere a C # / Java a livello di pacchetto in C ++. @jalf - che dire delle classi di amici (come una classe di fabbrica)?
Ogre Salmo33

1
@Ogre: e loro? Stai ancora specificatamente dando a quella classe e nessun altro accesso agli interni della classe. Non stai solo lasciando il cancello aperto per codice sconosciuto arbitrario da rovinare con la tua classe.
jalf

8

Ho trovato utile utilizzare l'accesso degli amici: funzioni private unittest.


Ma una funzione pubblica può essere utilizzata anche per questo? Qual è il vantaggio di utilizzare l'accesso degli amici?
Zheng Qu,

@Maverobot Potresti approfondire la tua domanda?
Vladimir

5

Friend è utile quando si crea un contenitore e si desidera implementare un iteratore per quella classe.


4

Abbiamo riscontrato un problema interessante in un'azienda in cui ho lavorato in precedenza in cui abbiamo usato l'amico per influire in modo decente. Ho lavorato nel nostro reparto framework creando un sistema di livello motore di base sul nostro sistema operativo personalizzato. Internamente avevamo una struttura di classe:

         Game
        /    \
 TwoPlayer  SinglePlayer

Tutte queste classi facevano parte del framework e gestite dal nostro team. I giochi prodotti dalla società sono stati costruiti sulla base di questo framework derivante da uno dei bambini di Games. Il problema era che Game aveva interfacce per varie cose a cui SinglePlayer e TwoPlayer avevano bisogno di accesso ma che non volevamo esporre al di fuori delle classi del framework. La soluzione era rendere private quelle interfacce e consentire a TwoPlayer e SinglePlayer di accedervi tramite amicizia.

Sinceramente tutto questo problema avrebbe potuto essere risolto da una migliore implementazione del nostro sistema ma eravamo bloccati in ciò che avevamo.


4

La risposta breve sarebbe: usare l' amico quando effettivamente migliora incapsulamento. Migliorare la leggibilità e l'usabilità (gli operatori << e >> sono l'esempio canonico) è anche una buona ragione.

Per quanto riguarda gli esempi di miglioramento dell'incapsulamento, le classi appositamente progettate per lavorare con gli interni di altre classi (vengono in mente le classi di test) sono buoni candidati.


"Gli operatori << e >> sono l'esempio canonico " No. Piuttosto esempi contrari canonici .
curiousguy,

@curiousguy: operatori <<e di >>solito sono amici, anziché membri, perché renderli membri li renderebbe scomodi da usare. Certo, sto parlando del caso in cui quegli operatori devono accedere ai dati privati; altrimenti, l'amicizia è inutile.
Gorpik,

" Perché rendendoli membri li renderebbe scomodi da usare. " Ovviamente, rendendo operator<<e operator>>membri della classe valore anziché non membri, o membri di i|ostream, non fornirebbero la sintassi desiderata, e sono non suggerendo esso. " Sto parlando del caso in cui quegli operatori devono accedere ai dati privati " Non capisco perché gli operatori di input / output debbano accedere ai membri privati.
curioso

4

Il creatore di C ++ afferma che non sta infrangendo alcun principio di incapsulamento e lo citerò:

L '"amico" viola l'incapsulamento? No non lo fa. "Friend" è un meccanismo esplicito per la concessione dell'accesso, proprio come l'appartenenza. Non puoi (in un programma conforme standard) concederti l'accesso a una classe senza modificarne l'origine.

È più che chiaro ...


@curiousguy: anche in caso di modelli, è vero.
Nawaz,

L'amicizia con il modello @Nawaz può essere concessa, ma chiunque può effettuare una nuova specializzazione parziale o esplicita senza modificare la classe di concessione dell'amicizia. Ma fai attenzione alle violazioni ODR quando lo fai. E non farlo comunque.
curioso

3

Un altro uso: amico (+ eredità virtuale) può essere usato per evitare di derivare da una classe (alias: "rendere una classe indivisibile") => 1 , 2

Da 2 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

3

Per fare TDD molte volte ho usato la parola chiave "amico" in C ++.

Un amico può sapere tutto di me?


Aggiornato: ho trovato questa preziosa risposta sulla parola chiave "amico" dal sito di Bjarne Stroustrup .

"Friend" è un meccanismo esplicito per la concessione dell'accesso, proprio come l'appartenenza.


3

Devi stare molto attento a quando / dove usi la friendparola chiave e, come te, l'ho usata molto raramente. Di seguito sono riportate alcune note sull'uso friende le alternative.

Diciamo che vuoi confrontare due oggetti per vedere se sono uguali. Puoi:

  • Usa i metodi di accesso per fare il confronto (controlla ogni ivar e determina l'uguaglianza).
  • In alternativa, è possibile accedere direttamente a tutti i membri rendendoli pubblici.

Il problema con la prima opzione è che potrebbero essere MOLTI accessori, che sono (leggermente) più lenti dell'accesso diretto alle variabili, più difficili da leggere e ingombranti. Il problema con il secondo approccio è che si rompe completamente l'incapsulamento.

Ciò che sarebbe bello sarebbe se potessimo definire una funzione esterna che potesse comunque ottenere l'accesso ai membri privati ​​di una classe. Possiamo farlo con la friendparola chiave:

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

Il metodo equal(Beer, Beer)ha ora accesso diretto ai ae b's membri privati (che può essere char *brand, float percentAlcohol, ecc Questo è un esempio alquanto, si dovrebbe prima applicare frienda un sovraccarico == operator, ma ci arriveremo.

Alcune cose da notare:

  • A friendNON è una funzione membro della classe
  • È una funzione ordinaria con accesso speciale ai membri privati ​​della classe
  • Non sostituire tutti gli accessori e i mutatori con gli amici (potresti anche fare tutto public!)
  • L'amicizia non è reciproca
  • L'amicizia non è transitiva
  • L'amicizia non è ereditata
  • Oppure, come spiega la FAQ di C ++ : "Solo perché ti concedo l'accesso di amicizia a me non concede automaticamente l'accesso ai tuoi figli a me, non concede automaticamente l'accesso ai tuoi amici a me e non mi concede automaticamente l'accesso a te ".

Lo uso davvero solo friendsquando è molto più difficile farlo nell'altro modo. Come altro esempio, le funzioni di molte matematiche vettoriali sono spesso creati come friendscausa l'interoperabilità dei Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4, ecc ed è solo molto più facile essere amici, piuttosto che dover usare di accesso in tutto il mondo. Come sottolineato, friendè spesso utile quando applicato a <<(davvero utile per il debug), >>e forse ==all'operatore, ma può anche essere usato per qualcosa del genere:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Come ho già detto, non lo uso friendmolto spesso, ma ogni tanto è proprio quello di cui hai bisogno. Spero che sia di aiuto!


2

Per quanto riguarda l'operatore << e l'operatore >> non vi sono buone ragioni per fare amicizia con questi operatori. È vero che non dovrebbero essere funzioni membro, ma non devono nemmeno essere amici.

La cosa migliore da fare è creare funzioni di stampa pubblica (ostream &) e lettura (istream &). Quindi, scrivi l'operatore << e l'operatore >> in termini di tali funzioni. Ciò offre l'ulteriore vantaggio di consentire di rendere virtuali quelle funzioni, fornendo una serializzazione virtuale.


" Per quanto riguarda l'operatore << e l'operatore >> non vi sono buone ragioni per fare amicizia con questi operatori. " Assolutamente corretto. " Ciò offre l'ulteriore vantaggio di consentire di rendere virtuali tali funzioni ". Se la classe in questione è destinata alla derivazione, sì. Altrimenti, perché preoccuparsi?
curiousguy,

Davvero non capisco perché questa risposta sia stata retrocessa due volte - e senza nemmeno una spiegazione! È scortese.
curioso

virtual aggiungerebbe un colpo
perfetto

2

Sto solo usando la parola chiave friend per sbloccare le funzioni protette. Alcuni diranno che non dovresti testare la funzionalità protetta. Tuttavia, trovo questo strumento molto utile quando si aggiungono nuove funzionalità.

Tuttavia, non uso la parola chiave direttamente nelle dichiarazioni di classe, ma utilizzo un template-hack per ottenere questo:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Questo mi permette di fare quanto segue:

friendMe(this, someClassInstance).someProtectedFunction();

Funziona su GCC e MSVC almeno.


2

In C ++ la parola chiave "amico" è utile per sovraccaricare l'operatore e fare Bridge.

1.) Parola chiave Friend nel sovraccarico dell'operatore:
Esempio di sovraccarico dell'operatore è: supponiamo di avere una classe "Punto" che ha due variabili float
"x" (per coordinata x) e "y" (per coordinata y). Ora dobbiamo sovraccaricare "<<"(operatore di estrazione) in modo tale che se lo chiamiamo "cout << pointobj"stamperà le coordinate xey (dove pointobj è un oggetto della classe Point). Per fare questo abbiamo due opzioni:

   1.Caricare la funzione "operator << ()" nella classe "ostream".
   2.Caricare la funzione "operator << ()" nella classe "Point".
Ora la prima opzione non è buona perché se dovessimo sovraccaricare nuovamente questo operatore per una classe diversa, dovremo apportare di nuovo modifiche alla classe "ostream".
Ecco perché la seconda è l'opzione migliore. Ora il compilatore può chiamare la "operator <<()"funzione:

   1.Utilizzando l'oggetto ostream cout.As: cout.operator << (Pointobj) (forma ostream class). 
2.Chiama senza oggetto. Come: operatore << (cout, Pointobj) (dalla classe Point).

Perché abbiamo implementato il sovraccarico in classe Point. Quindi per chiamare questa funzione senza un oggetto dobbiamo aggiungere una "friend"parola chiave perché possiamo chiamare una funzione amico senza un oggetto. Ora la dichiarazione di funzione sarà la seguente:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Parola chiave Friend nel fare bridge:
Supponiamo di dover creare una funzione in cui dobbiamo accedere a un membro privato di due o più classi (generalmente chiamato "bridge"). Come fare:
Per accedere a un membro privato di una classe dovrebbe essere membro di quella classe. Ora per accedere a un membro privato di un'altra classe ogni classe dovrebbe dichiarare quella funzione come funzione amico. Ad esempio: supponiamo che ci siano due classi A e B. Una funzione "funcBridge()"desidera accedere a un membro privato di entrambe le classi. Quindi entrambe le classi dovrebbero dichiarare "funcBridge()"come:
friend return_type funcBridge(A &a_obj, B & b_obj);

Penso che questo aiuterebbe a capire la parola chiave amico.


2

Come dice il riferimento per la dichiarazione di amicizia :

La dichiarazione di amicizia appare in un corpo di classe e concede a una funzione oa un'altra classe l'accesso a membri privati ​​e protetti della classe in cui appare la dichiarazione di amico.

Quindi, come promemoria, ci sono errori tecnici in alcune delle risposte che dicono che friendpossono visitare solo i membri protetti .


1

L'esempio dell'albero è un buon esempio: avere un oggetto implementato in alcune classi diverse senza avere una relazione di ereditarietà.

Forse potresti anche averne bisogno per proteggere un costruttore e costringere le persone a usare la tua fabbrica di "amici".

... Ok, francamente puoi vivere senza di essa.


1

Per fare TDD molte volte ho usato la parola chiave "amico" in C ++.
Un amico può sapere tutto di me?

No, è solo un'amicizia a senso unico: `(


1

Un'istanza specifica in cui utilizzo friendè la creazione di classi Singleton . La friendparola chiave mi permette di creare una funzione di accesso, che è più concisa che avere sempre un metodo "GetInstance ()" sulla classe.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Questa potrebbe essere una questione di gusti, ma non credo che salvare qualche sequenza di tasti giustifichi l'uso dell'amico qui. GetMySingleton () dovrebbe essere un metodo statico della classe.
Gorpik,

Il c-tor privato non consentirebbe una funzione non amico per istanziare MySingleton, quindi la parola chiave amico è necessaria qui.
JBR Wilkinson,

@Gorpik " Questa potrebbe essere una questione di gusti, ma non credo che il salvataggio di alcune sequenze di tasti giustifichi l'uso dell'amico qui. " Ad ogni modo, friendnon è necessaria una "giustificazione" particolare, quando non si aggiunge una funzione membro.
curiousguy il

I singleton sono comunque considerati una cattiva pratica ("singleton dannoso" di Google e otterrai molti risultati come questo . Non credo che l'uso di una funzione per implementare un antipattern possa essere considerato un buon uso di quella funzione.
weberc2

1

Le funzioni e le classi degli amici forniscono accesso diretto ai membri privati ​​e protetti della classe per evitare di rompere l'incapsulamento nel caso generale. La maggior parte dell'utilizzo è con ostream: vorremmo essere in grado di digitare:

Point p;
cout << p;

Tuttavia, ciò potrebbe richiedere l'accesso ai dati privati ​​di Point, quindi definiamo l'operatore sovraccarico

friend ostream& operator<<(ostream& output, const Point& p);

Vi sono tuttavia ovvie implicazioni sull'incapsulamento. Innanzitutto, ora la classe o la funzione di amico ha pieno accesso a TUTTI i membri della classe, anche a quelli che non riguardano i suoi bisogni. In secondo luogo, le implementazioni della classe e dell'amico ora sono intrappolate al punto che un cambiamento interno nella classe può spezzare l'amico.

Se vedi l'amico come un'estensione della classe, questo non è un problema, logicamente parlando. Ma, in quel caso, perché era necessario spearare l'amico in primo luogo.

Per ottenere la stessa cosa che gli "amici" pretendono di ottenere, ma senza interrompere l'incapsulamento, si può fare questo:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

L'incapsulamento non è interrotto, la classe B non ha accesso all'implementazione interna in A, tuttavia il risultato è lo stesso di se avessimo dichiarato B amico di A. Il compilatore ottimizzerà le chiamate di funzione, quindi ciò comporterà lo stesso istruzioni come accesso diretto.

Penso che usare "amico" sia semplicemente una scorciatoia con vantaggi discutibili, ma a costi definiti.


1

Questo potrebbe non essere un caso d'uso reale, ma può aiutare a illustrare l'uso dell'amico tra le classi.

The ClubHouse

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

La classe dei membri

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Servizi

class Amenity{};

Se guardi la relazione di queste classi qui; il ClubHouse detiene una varietà di diversi tipi di abbonamenti e accesso ai soci. I membri derivano tutti da una classe super o base poiché condividono tutti un ID e un tipo elencato che è comune e le classi esterne possono accedere ai loro ID e tipi tramite le funzioni di accesso che si trovano nella classe base.

Tuttavia, attraverso questo tipo di gerarchia dei membri e delle sue classi derivate e delle loro relazioni con la classe ClubHouse, l'unica delle classi derivate che ha "privilegi speciali" è la classe VIPMember. La classe base e le altre 2 classi derivate non possono accedere al metodo joinVIPEvent () del ClubHouse, tuttavia la classe Membro VIP ha quel privilegio come se avesse accesso completo a quell'evento.

Pertanto, con VIPMember e ClubHouse è una strada a doppio senso di accesso in cui le altre classi di membri sono limitate.


0

Durante l'implementazione di algoritmi ad albero per la classe, il codice quadro che il prof ci ha fornito aveva la classe ad albero come amico della classe nodo.

In realtà non fa nulla di buono, oltre a consentire l'accesso a una variabile membro senza utilizzare una funzione di impostazione.


0

Puoi usare l'amicizia quando classi diverse (che non ereditano l'una dall'altra) utilizzano membri privati ​​o protetti dell'altra classe.

I casi d'uso tipici delle funzioni degli amici sono operazioni condotte tra due diverse classi che accedono a membri privati ​​o protetti di entrambi.

da http://www.cplusplus.com/doc/tutorial/inheritance/ .

Puoi vedere questo esempio in cui il metodo non membro accede ai membri privati ​​di una classe. Questo metodo deve essere dichiarato in questa stessa classe come amico della classe.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

Probabilmente ho perso qualcosa dalle risposte sopra, ma un altro concetto importante nell'incapsulamento sta nascondendo l'implementazione. Ridurre l'accesso ai membri di dati privati ​​(i dettagli di implementazione di una classe) consente in seguito una modifica molto più semplice del codice. Se un amico accede direttamente ai dati privati, eventuali modifiche ai campi dei dati di implementazione (dati privati), interrompi il codice accedendo a tali dati. L'uso dei metodi di accesso lo elimina principalmente. Abbastanza importante, penso.


-1

Si potrebbe aderire ai principi più stretto e più pura OOP e garantire che nessun membro di dati per ogni classe hanno anche funzioni di accesso in modo che tutti gli oggetti devono essere gli unici in grado di conoscere i loro dati con l'unico modo per agire su di essi è attraverso indiretti messaggi , cioè, metodi.

Ma anche C # ha una parola chiave di visibilità interna e Java ha l' accessibilità a livello di pacchetto predefinita per alcune cose. Il C ++ si avvicina in realtà all'ideale OOP minimizzando il compromesso della visibilità in una classe specificando esattamente quale altra classe e solo le altre classi potrebbero vederla.

In realtà non uso C ++ ma se C # avesse degli amici , lo farei invece del modificatore interno assembly-global , che in realtà uso molto. Non interrompe realmente l'incapsulamento, poiché l'unità di distribuzione in .NET è un assembly.

Ma poi c'è l' InternalsVisibleTo Attribute (otherAssembly) che agisce come un meccanismo amico cross-assembly . Microsoft lo utilizza per gli assembly del visual designer .


-1

Gli amici sono utili anche per i callback. È possibile implementare callback come metodi statici

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

dove callbackchiama localCallbackinternamente e clientDatacontiene l'istanza. Secondo me,

o...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

Ciò consente all'amico di essere definito puramente nel cpp come una funzione di tipo c, e non ingombrare la classe.

Allo stesso modo, uno schema che ho visto molto spesso è quello di mettere tutti i membri veramente privati ​​di una classe in un'altra classe, che è dichiarata nell'intestazione, definita nel cpp, e amica. Ciò consente al programmatore di nascondere gran parte della complessità e del funzionamento interno della classe all'utente dell'intestazione.

Nell'intestazione:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

Nel cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Diventa più facile nascondere cose che il downstream non ha bisogno di vedere in questo modo.


1
Le interfacce non sarebbero un modo più pulito per raggiungere questo obiettivo? Cosa impedisce a qualcuno di cercare MyFooPrivate.h?
JBR Wilkinson,

1
Bene, se stai usando privati ​​e pubblici per mantenere i segreti, verrai sconfitto facilmente. Con "nascondere", intendo, l'utente di MyFoo non ha davvero bisogno di vedere i membri privati. Oltre a questo, è utile mantenere la compatibilità ABI. Se rendi _private un puntatore, l'implementazione privata può cambiare quanto vuoi, senza toccare l'interfaccia pubblica, mantenendo così la compatibilità ABI.
Shash,

Ti riferisci al linguaggio PIMPL; lo scopo per il quale non è l'incapsulamento aggiuntivo come sembra stia dicendo, ma di spostare i dettagli dell'implementazione fuori dall'intestazione, quindi la modifica di un dettaglio dell'implementazione non impone una ricompilazione del codice client. Inoltre, non è necessario utilizzare amico per implementare questo linguaggio.
weberc2,

Beh si. Il suo scopo principale è spostare i dettagli di implementazione. L'amico è utile per gestire i membri privati ​​all'interno della classe pubblica da quelli privati ​​o viceversa.
Shash
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.