Newsgroup: it.comp.lang.c++
Titolo: costruttori di copia...

Ho trovato un comportamento "strano" nel g++ (2.96) e
mi chiedevo se era un problema di questo compilatore 
o se si tratta di qualcosa prevista dallo standard.
Per quanto ne sapevo, data una classe A per cui e' 
definito un costruttore con un solo argomento  di 
tipo B, questo costruttore verra' invocato anche 
quando un oggetto di classe A e' istanziato 
mediante assegnazione di un oggetto di classe B, 
esempio:
class A
{
  public:
    A(B b);
};
int main()
{
  B b;
  A a = b;
|
questa notazione equivale a 
int main()
{
  B b
  A a(b);
}
Almeno questo e' quello che credevo...
In effetti, provando ad eseguire il seguente programmino
-------------------------------------------------------------------
 1: #include <iostream>
 2:
 3: class B 
 4: {
 5:   public:
 6:     B()  { std::cout << "B::B()" << std::endl; }
 7:     ~B() { std::cout << "B::~B()" << std::endl; }
 8: };
 9:
10: class A
11: {
12:     B * b;
13:   public:
14:     A(const A&) 
15:	{  std::cout << "A::A(const A&)" << std::endl; }
16:     A& operator=(const A&) 
17:	{ std::cout << "A::operator=(const A&)" << std::endl; 
18:	  return this; }
19:
20:     A(B *p) : b(p) { std::cout << "A::A(B *)" << std::endl; }
21:     ~A() { std::cout << "A::~A()" << std::endl ; delete b; }
22: };
23:
24: int main()
25: {
26:        A a = new B;
27: }
-------------------------------------------------------------------
si ottiene, come previsto il seguente output:
B::B()
A::A(B *)
A::~A()
B::~B()
prima viene costruito B con la new ( output:  "B::B()" )
quindi viene invocato il costruttore di A ( output "A::A(B *)" )
quindi viene incocato il distruttore di A ( "A::~A()" ) che
a sua volta distrugge B ( "B::~B()" ).
Ne' l'operator= ne' il costruttore di copia vengono usati
(N.B. la loro implementazione e' volutamente incompleta, li ho messi
solo per essere sicuro che non vengono invocati)
Fin qui, niente di piu' chiaro (almeno spero).
Se pero' il costruttore di copia e l'operatore di assegnamento
vengono dichiarati privati...(in effetti non voglio che la classe
A venga copiata), il compilatore si rifiuta di compilare il programma,
$ g++ ctor.cpp
ctor.cpp: In function `int main()':
ctor.cpp:14: `A::A (const A &)' is private
ctor.cpp:26: within this context
A giudicare dall'errore si direbbe che il compilatore voglia provare
a costruire un temporaneo A col costruttore A(B*) e assegnare questo
temporaneo alla variabile a mediante il costruttore di copia.
Ma, non essendo il costruttore di copia disponibile, si blocca con
questo
errore.
Magari, quando il costruttore di copia e' pubblico, questa copia
superflua
viene eliminata in fase di ottimizzazione (?)
Tutto funziona regolarmente, se il costruttore di copia e' privato ma
la classe A si istanzia con
A a(new B);
in questo caso il costruttore A::A(B *) viene invocato esplicitamente e
non c'e' alcun bisogno del costruttore di copia.
Commenti ?
/fabio

 Commenti ?
Uno solo: ancora una volta si dimostra la mia vecchia tesi
che il C++ e` bellissimo, ma un poco troppo complicato per
essere usato da meri essere umani come noialtri.  Per i
marziani immagino che vada benissimo, ovviamente.  Peccato
anche che sulla Terra non esista un altro linguaggio che
sia altrettanto portatile, altrettanto ottimizzante,
altrettanto produttivo, altrettanto diffuso, per cui (per
quel 10%-20% di codice che uno scrive, che *DEVE* avere
le prestazioni massime assolute possibili senza potere
sacrificare portabilita` ecc) sempre sul C++ si ricade.
(Anche qui immagino che i marziani siano messi meglio).
Per fortuna che l'80%-90% del codice che uno scrive non
ha tutti questi strettissimi requisiti, per cui rimane
possibile usare un linguaggio umanamente comprensibile
per la *maggior parte* del proprio lavoro...!-)
Alex (MVP di Brainbench per il C++)

 }
Non e' esatto. Nel primo caso lo standard prevede la costruzione di un
temporaneo di tipo a e quindi la costruzione col costruttore di copia,
mentre nel secondo caso la costruzione e' diretta.
Nel primo caso l'implementazione e' libera di eliminare per
ottimizzazione la costruzione per copia, ma deve segnalare un errore se
tale costruttore non e' accessibile. Di qui il resto.
Ciao,
Nicola

 Nicola
Era come immaginavo, ma questo non comporta un "undefined behaviour" ?
Il costruttore di copia e il distruttore potrebbero avere effetti
collaterali
ma verrebbero essere eseguiti o meno a seconda del comportamento del
compilatore.
Nell'esempio che ho postato il distruttore del temporaneo distruggerebbe
il puntatore se non venisse ottimizzato via dal compilatore.
Il codice da cui e' sorto il problema era una cosa del genere:
HttpRequest request = server->getConnection();
che ho dovuto sostituire con
Conncection *conn = server->getConnection();
HttpRequest request(conn);
oppure col piu' compatto, ma meno leggibile
HttpRequest request(server->getConnection());
/fabio

non corretto.  E quindi...
una domanda: tempo fa lessi su questo ng che, qualunque ottimizzazione
possa compiere il compilatore, gli "effetti visibili" del codice
devono essere garantiti. In questo caso nel costruttore di copia c'č
un bel cout<< "...", ovvero un effetto visibile. Com'č allora che il
compilatore ottimizza lo stesso?
Anto
--
Antonio Cuni, 19 anni, studente
Brainbench Certification: 
Master C++: 4.6, higher than 98% of all previous test takers.
C Language: 3.32, higher than 79% of all previous test takers.
http://www.brainbench.com/transcript.jsp?pid=1301688

 compilatore ottimizza lo stesso?
Perche' in questo caso e' esplicitamente autorizzato dallo standard.
Affari del programmatore se viola la semantica del costruttore di copia
:-)
Ciao,
Nicola

:-)
quale sarebbe la semantica precisa del copy ctor?


quale sarebbe la semantica precisa del copy ctor?
Il copy constructor deve costruire un oggetto che sia
la copia esatta dell'oggetto passato; l'operatore di
assegnazione deve effettuare una copia dell'oggetto
passato sull'istanza.
In C++ gli altri operatori (a parte qualche altra
anomalia usata molto raramente come operator&) non
hanno una semantica associata e possono fare quello
che si vuole. operator++ potrebbe ad esempio azzerare
un oggetto e operator/ potrebbe essere benissimo
non-const e fare in modo che a/b modifichi a.
Il costruttore di copia e l'operatore di assegnazione
sono invece utilizzati dal C++ stesso e dalle librerie
con la semantica assegnata. Ad esempio la libreria
standard chiama in continuazione questi membri per
le istanze presenti nei container e per gli algoritmi
e il compilatore chiama il costruttore di copia nel
passaggio degli argomenti per valore.
L'operatore di assegnazione e' poi anche "anomalo"
perche' non viene ereditato (come gli altri operatori)
ma ne viene sintetizzato uno con la semantica richiesta
se il programmatore non ne fornisce esplicitamente una
propria versione. Anche il costruttore di copia e'
"anomalo" in quanto viene sintetizzato anche se in una
classe esistono altri costruttori (cosa che ad esempio
non succede con il costruttore di default).
Visto che in questi casi e' definita una semantica
precisa lo standard ha deciso di permettere esplicitamente
le ottimizzazioni che non dovrebbero avere impatti
dal punto di vista logico.
Un discorso a parte e' quello di auto_ptr (che
personalmente trovo abbastanza "bruttino") in cui
sia l'assegnazione che il copy constructor hanno
significati anomali (con di conseguenza riflessi
molto importanti su cosa si puo' e cosa non si puo'
fare con un auto_ptr).
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 fare con un auto_ptr).
questa cosa mi interessa... anche perche' auto_ptr lo trovo utile...
ma ovviamente non so nulla di queste limitazioni! :-(
potresti spiegarle? grazie! :-)
--
Ciao by Beppe

potresti spiegarle? grazie! :-)
La semantica di copia "alterata" implica che un auto_ptr
non puo' essere messo ad esempio in un container standard.
Ci sono usi interessanti come il tornare un auto_ptr
da una funzione che esegue una allocazione dinamica,
oppure passare auto_ptr a delle funzioni con un
trasferimento di ownership dell'oggetto... resta il
fatto che bisogna stare attenti che quando si scrive
a=b si modificano sia a che b e che b non punta piu'
a nulla.
A volte il fatto che siano presenti delle operazioni di
copia (che con gli auto_ptr copie non sono) puo' essere
subdolo (es. cambia la semantica se una funzione riceve
come parametro un const& invece che una istanza di T
se T e' un auto_ptr)...
  auto_ptr<MyClass> p(new MyClass());
  f(p);
  p->DoIt();
  // Lecito ? se f e' stata dichiarata ricevere
  // un "const auto_ptr<MyClass>&" si, se invece
  // la dichiarazione del parametro e'
  // "auto_ptr<MyClass>" allora no.
Io di solito preferisco smart ptr piu' costosi e/o
intrusivi (reference counting) ma che non hanno questa
limitazione.
Chiaro che nell'utilizzare auto_ptr semplicemente come
variabili locali per gestire la deallocazione automatica
non ci vedo nessun problema.


 non ci vedo nessun problema.
thank you! adesso e' tutto molto piu' chiaro! :-)
--
Ciao by Beppe