Perché posso accedere alle variabili private nel costruttore di copie?


88

Ho imparato che non posso mai accedere a una variabile privata, solo con una funzione get nella classe. Ma allora perché posso accedervi nel costruttore di copie?

Esempio:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

La mia dichiarazione:

private:
  T *pFirst,*pLast,*pEnd;

Perché il costruttore di copie è un membro della classe per impostazione predefinita, così come alcuni altri.
DumbCoder

+ 53 / -0? Chi ha votato per questo? In quale altro modo li copieresti ?!? (Sfatiamo le non alternative: crea un getter di riferimento pubblico per ogni membro privato? Quindi non sono affatto privati. Crea un const&getter pubblico o per valore per ciascuno? Quindi sono solo "write-private", & perché i valori sprecano risorse e falliscono per i membri non copiabili.) Sono sconcertato da un tale successo di una domanda così vacua, chiedendo informazioni sulla costruzione della copia ignorando totalmente cosa significa, e nessuna risposta usa la logica di base per sfatarla. Spiegano i tecnicismi asciutti, ma c'è una risposta molto più semplice a una domanda che ha lampeggiato
underscore_d

9
@underscore_d, "In quale altro modo li copieresti?" è una risposta molto strana secondo me. È come rispondere "come funziona la gravità?" con "come altrimenti cadrebbero le cose!" Confondere l'incapsulamento a livello di classe con l'incapsulamento a livello di oggetto è infatti abbastanza comune. È divertente come sembri pensare che sia una domanda stupida e che la risposta dovrebbe essere ovvia. Tieni presente che l'incapsulamento in Smalltalk (probabilmente l'archetipo del linguaggio OO) funziona effettivamente a livello di oggetto.
aioobe

@aioobe Buon punto, grazie. Il mio commento è piuttosto estremo: forse quel giorno la macchina del caffè era rotta. Apprezzo che tu abbia sottolineato perché questa domanda sarebbe popolare, specialmente tra coloro che provengono da altre (e forse più) lingue OO. In effetti, è discutibile che il mio commento sia stato la cosa che è stata "sbattuta", poiché stavo scrivendo dal punto di vista di qualcuno che programma principalmente in C ++. Inoltre, adoro l'analogia della gravità!
underscore_d

Risposte:


33

Secondo me, le risposte esistenti fanno un pessimo lavoro nello spiegare il "perché" di questo - concentrandosi troppo sul ribadire quale comportamento sia valido. "i modificatori di accesso funzionano a livello di classe e non a livello di oggetto." - si ma perché?

Il concetto generale qui è che sono i programmatori che progettano, scrivono e mantengono una classe che devono comprendere l'incapsulamento OO desiderato e autorizzato a coordinarne l'implementazione. Quindi, se stai scrivendo class X, stai codificando non solo come un singolo X xoggetto può essere utilizzato dal codice con accesso ad esso, ma anche come:

  • le classi derivate sono in grado di interagire con esso (tramite funzioni virtuali opzionali pure e / o accesso protetto), e
  • Xoggetti distinti cooperano per fornire i comportamenti previsti rispettando le post-condizioni e le invarianti dal tuo progetto.

Non è nemmeno solo il costruttore di copie: molte operazioni possono coinvolgere due o più istanze della tua classe: se stai confrontando, aggiungendo / moltiplicando / dividendo, costruendo copie, clonando, assegnando ecc., Allora è spesso il caso che tu o semplicemente deve avere accesso a dati privati ​​e / o protetti nell'altro oggetto, o desidera che consenta un'implementazione della funzione più semplice, più veloce o generalmente migliore.

In particolare, queste operazioni potrebbero voler sfruttare l'accesso privilegiato per fare cose come:

  • (costruttori di copia) usano un membro privato dell'oggetto "rhs" (lato destro) in un elenco di inizializzatori, in modo che una variabile membro sia essa stessa costruita in copia invece che costruita in modo predefinito (se anche legale) e quindi assegnata (di nuovo, se legale)
  • condividere risorse: handle di file, segmenti di memoria condivisa, messaggi shared_ptrdi posta elettronica per fare riferimento ai dati ecc
  • assumere la proprietà delle cose, ad esempio auto_ptr<>"sposta" la proprietà sull'oggetto in costruzione
  • copiare "cache" privata, calibrazione o membri di stato necessari per costruire il nuovo oggetto in uno stato utilizzabile in modo ottimale senza doverli rigenerare da zero
  • copia / accedi alle informazioni di diagnostica / traccia conservate nell'oggetto che viene copiato che non sono altrimenti accessibili tramite API pubbliche ma potrebbero essere utilizzate da qualche oggetto di eccezione o logging successivo (ad esempio qualcosa sul tempo / le circostanze in cui l'istanza "originale" non costruita dalla copia fu costruito)
  • eseguire una copia più efficiente di alcuni dati: ad esempio, gli oggetti possono avere, ad esempio, un unordered_mapmembro ma esporre pubblicamente solo begin()e end()iteratori - con accesso diretto a size()voi potrebbe essere possibile reserveuna copia più rapida; peggio ancora se solo espongono at()e insert()ed altro throw....
  • copiare i riferimenti agli oggetti padre / coordinamento / gestione che potrebbero essere sconosciuti o di sola scrittura per il codice client

2
Penso che il più grande "perché" sia che sarebbe un enorme sovraccarico di runtime per controllare se this == otherogni volta che accedi a other.xquale dovresti se i modificatori di accesso funzionassero a livello di oggetto.
aioobe

2
@aioobe Penso che la tua risposta dovrebbe essere molto, molto più prominente. La risposta di Whilel Tony è davvero buona e concettuale, se fossi un uomo di scommesse, scommetterei che la tua risposta è il vero motivo storico della scelta. Non solo è più performante, ma è anche molto più semplice. Sarebbe un'ottima domanda per Bjarne!
Nir Friedman

Ho segnato la tua risposta, perché spiega i precedenti;)
demonizzazione il

@demonking, penso che le ragioni fornite in questa risposta coprano il motivo per cui è conveniente lasciare che i dati privati ​​siano aperti ad altri oggetti. Ma i modificatori di accesso non sono pensati per rendere i dati "apertamente" sufficienti. Sono piuttosto pensati per rendere i dati abbastanza chiusi per l'incapsulamento. (In termini di praticità , sarebbe ancora meglio se le variabili private fossero pubbliche!) Ho aggiornato la mia risposta con una sezione che penso affronti meglio il vero motivo .
aioobe

@ aioobe: vecchi commenti, ma comunque ... "controlla se this == otherogni volta che accedi other.x" - manca il punto - se other.xfosse accettato solo in fase di esecuzione quando è equivalente a this.x, non ci sarebbero molti puntatori scritti other.xin primo luogo; il compilatore potrebbe anche costringerti a scrivere if (this == other) ...this.x...per qualunque cosa tu stia per fare. Anche la tua concezione della "convenienza (ancor di più se le variabili private fossero pubbliche)" manca il punto: il modo in cui lo Standard è definito è abbastanza restrittivo da consentire un adeguato incapsulamento, ma non inutilmente scomodo.
Tony Delroy

109

I modificatori di accesso funzionano a livello di classe e non a livello di oggetto .

Cioè, due oggetti della stessa classe possono accedere a dati privati ​​reciproci.

Perché:

Principalmente a causa dell'efficienza. Sarebbe un sovraccarico di runtime non trascurabile controllare se this == otherogni volta che accediother.x cui dovresti farlo se i modificatori di accesso funzionassero a livello di oggetto.

È anche un po 'semanticamente logico se ci si pensa in termini di scoping: "Quanta parte del codice devo tenere a mente quando modifico una variabile privata?" - È necessario tenere presente il codice dell'intera classe, che è ortogonale a cui esistono gli oggetti in runtime.

Ed è incredibilmente conveniente quando si scrivono costruttori di copia e operatori di assegnazione.


35

Puoi accedere ai membri privati ​​di una classe dall'interno della classe, anche a quelli di un'altra istanza.


10

Per capire la risposta, vorrei ricordarvi alcuni concetti.

  1. Non importa quanti oggetti crei, c'è solo una copia di una funzione in memoria per quella classe. Significa che le funzioni vengono create solo una volta. Tuttavia le variabili sono separate per ogni istanza della classe.
  2. this il puntatore viene passato a ogni funzione quando viene chiamato.

Ora è a causa del this puntatore, la funzione è in grado di individuare le variabili di quella particolare istanza. non importa se è privato o pubblico. è possibile accedervi all'interno di quella funzione. Ora se passiamo un puntatore a un altro oggetto della stessa classe. utilizzando questo secondo puntatore potremo accedere ai membri privati.

Spero che questo risponda alla tua domanda.


6

Il costruttore di copia è la funzione membro della classe e come tale ha accesso ai membri dati della classe, anche quelli dichiarati come "privati".


4
Come qualcuno che conosce la lingua, capisco cosa intendi. Tuttavia, se non conoscessi la lingua, avrei pensato che mi stessi solo ripetendo la domanda.
San Jacinto
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.