Newsgroup: it.comp.lang.c++
Titolo: Problema VC++: doppio costruttore

Ho una classe X con due costruttori di copia che differiscono solo per
la qualifica "const" sul parametro:
class X
{
  ...
public:
  ...
  X(const X&x) {...}
  X(X&x) {...}
};
Con gcc va tutto bene, mentre con VC++ il compilatore mi segnala che per
lui i costruttori di copia sono uguali e che usera' sempre il primo.
Lo stesso accade per operator =.
C'e' modo di fargli capire che sono diversi?
Nel mio caso perdo solo delle ottimizzazioni, ma mi viene da pensare che
in altri contesti i due costruttori potrebbero fare cose MOLTO diverse e
il codice prodotto risultare sbagliato.
Sono io fuori standard o, come al solito, VC++? Cosa dice lo standard a
riguardo?
 bye & Thanks
	av.

 lui i costruttori di copia sono uguali e che usera' sempre il primo.
Che versione hai?
Sicuro che non ti segnali la warning ma poi usi entrambi a seconda del
caso?!
Hai fatto delle prove?!
Tommy


No, non ho provato.
Tuttavia la spiegazione del messaggio di errore e':
There were multiple copy constructors of a single type specified for the
given class. The first constructor was used.
Questo usando VC++ 6.0 Enterprise SP5.
 bye & Thanks
	av.

 Sono io fuori standard o, come al solito, VC++?
Non e' colpa del VC++ ne tanto meno della Microsoft. La colpa e' tua che usi
un compilatore vecchio!
(se si puo' parlare di colpe....)
Tommy


Se fosse per me non utilizzerei neanche VC++. Purtroppo e' il cliente
che mi impone questo compilatore e questa versione :(
Per fortuna riesco a lavorare con gcc e controllare alla fine che anche
VC++ compili, altrimenti diventerei pazzo!
 bye & Thanks
	av.

Beh tecnicamente è corretto il comportamento di VC++.
L'overloading si basa sul numero e sui tipi dei parametri passati, di
conseguenza il qualificatore const indica solo che il parametro non verrà
modificato, ma il tipo passato è sempre lo stesso!
--------------------------------
Inviato via 
http://usenet.libero.it

 VC++ compili, altrimenti diventerei pazzo!
cosa usi come IDE per il gcc ???
poi scusami perche' se tanto gli sviluppi un sw ti obliga sul vc ???
soprattutto vecchio ...
tanto non usa solo gli exe ???
ciao

 tanto non usa solo gli exe ???
No. Sviluppo solo alcune librerie; il programma principale lo sviluppano
loro stessi, altre librerie sono fatte da altri collaboratori oppure
comprate gia' compilate. Tutti usano VC e come e' noto non c'e'
compatibilita' in fase di linking tra compilatori C++ diversi.
 bye
	av.

 modificato, ma il tipo passato è sempre lo stesso!
Sei sicuro di questo?
Hai riferimenti al documento dello standard dove lo si dica?
 bye
	av.

No non sò se è standard o no!
Però su libri che ho studiato, c'è scritto che un limite dell'overloading
delle funzioni è rappresentato dal tipo reference che passato come parametro
non è differente da un parametro dello stesso tipo ma non reference.
Questo su un libro di VC++


 Questo su un libro di VC++
Ecco, appunto. La mia era una domanda sul C++ non su VC++.
 bye & Thanks
	av.

 modificato, ma il tipo passato è sempre lo stesso!
La qualificazione 'const' degli argomenti svolge invece un ruolo ben preciso
nel processo di selezione dell'overload. Se due funzioni candidate
differiscono solo per il 'const' di un puntatore (nel tipo a cui il
puntatore punta) o di un reference, quella senza 'const' è considerata
migliore e viene selezionata. La stessa cosa accade per le funzioni membro
const/non-const, dove la const-ness interessata è quella di 'this' (che
potremmo considerare un parametro implicito della funzione).
Si veda:
 13.3      Overload resolution
 13.3.3    Best viable function
 13.3.3.2  Ranking implicit conversion sequences
Il seguente è un esempio riportato nello Standard:
                  int f(const int &);
                  int f(int &);
                  int g(const int &);
                  int g(int);
                  int i;
                  int j = f(i);     // Calls f(int &)
                  int k = g(i);     // ambiguous
Max

 potremmo considerare un parametro implicito della funzione).
Ok, allora su questo siamo d'accordo.
Scusa la pedanteria, ma allora confermi che dovrebbe chiamare i due
costruttori diversi nei due casi in cui usi un oggetto const e
non-const?
 bye & Thanks
	av.

 non-const?
Sì, non mi pare che i costruttori ricevano un trattamento speciale in questo
caso.
Non riesco, però, a immaginare buone ragioni per avere due siffatti
costruttori copia, che mi pare rischino soltanto di compromettere la
leggibilità del codice. Esattamente, qual è il motivo per cui avresti tale
esigenza?
Max


Mi aspettavo questa domanda :)
L'oggetto in questione contiene alcuni puntatori ad array di oggetti
abbastanza vasti; la copia pura e semplice implica pertanto copiare
tutti questi oggetti ed e' quindi costosa.
Ho notato che spesso mi capita di copiare un oggetto su un altro e
quindi subito dopo distruggere l'originale.
Ho percio realizzato un meccanismo di trasferimento per possesso: prima
di fare la copia invoco un metodo (che ho chiamato LeaveOwnership),
questo accende un flag che fa si che il successivo costruttore di copia
trasferisca semplicemente i puntatori all'oggetto nuovo, anziche'
copiare tutti gli array. Ovviamente ho avuto cura di far si che il
distruttore poi non deallochi piu' tale memoria.
Ovvio che questo lo posso sintatticamente fare solo su oggetti non
const, per cui mi servono i due costruttori, altrimenti, quella volta
che ho un oggetto const, il compilatore da errore.
Non so se ci sono altri metodi migliori per fare quello che voglio.
Questo alla fine mi sembrava molto "trasparente" dal punto di vista
dell'utilizzatore della libreria, il quale puo' anche non saper nulla di
questo meccanismo e lavorare come se niente fosse; se invece vuole
ottimizzare deve solo inserire una chiamata.
 bye & Thanks
	av.

 ottimizzare deve solo inserire una chiamata.
Però, se l'utilizzatore (che desidera l'ottimizzazione) deve comunque
invocare un metodo prima di fare la copia, il sistema non è poi tanto
trasparente. A questo punto, perché allora non prevedere un opportuno
costruttore, con signature speciale, che non si confonda con il costruttore
copia?
 Ad esempio:
  class X
  {
   public:
    enum leave_ownership_tag { LeaveOwneship };
    X( const X& x ) { /* ... */}
    X( X& x, leave_ownership_tag ) { /* ... */}
 };
  X a;
  X b(a);
  X c(a,X::LeaveOwneship)
Max
 bye & Thanks
av.

 compatibilita' in fase di linking tra compilatori C++ diversi.
... anche se distribuisci la tua libreria come dll ??? cioe' magari come
.lib statica almeno la inglobano senza problemi no ??? cosi' facendo non
avresti piu' problemi e torneresti ad usare il compilatore preferito no ?
ciao


No. Faccio infatti una dll, ma non capisco come questo possa risolvere
il problema. L'interfaccia e' sempre quella.
 bye
	av.

   X c(a,X::LeaveOwneship)
A questo non avevo pensato.
Grazie mille, ora provo.
 bye
	av.

 modificato, ma il tipo passato è sempre lo stesso!
assolutamente no. Il qualificatore const fa parte a tutti gli effetti del
tipo, quindi l'overloading e` possibile. VC++ sbaglia, come tante altre
volte del resto.
ciao Anto
-- 
"Computer science is not about computers any more than astronomy
is about telescopes." -- EW Dijkstra

 volte del resto.
VC++ 6 sbaglia! Stiamo parlando di una versione che ha 6 anni. Prova ad
usare gcc di 6 anni fa, poi ne riparliamo!
VC++ .NET 2003 sono sicuro faccia la cosa giusta. Mi sembra inutile negare
che VC++ (grazie alla nuova polica Microsoft e a Sutter) stia diventando uno
dei migliori compilatori di C++.
Tommy

 dei migliori compilatori di C++.
Tralascio le opinioni personali e mi limito a dire che non era questo il
punto saliente del mio messaggio.
 bye
	av.

 punto saliente del mio messaggio.
Ah, e quale era?! Che un compilatore del '97 dovrebbe essere in grado di
compilare il C++ seguendo lo standard del '98 ?! (ci vorrebbero dei
cartomanti ;-)
Tommy

 Come puoi vedere non e' che il mio gcc sia particolarmente nuovo.
C:\**>g++ --version
g++ (GCC) 3.2.3 (mingw special 20030504-1)
Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
aggiornarsi un po ??? e gia' che il mingw rimane un po indietro ... ma per
linux dovresti avere i compilatori e le cros compiling piu' recenti ...
come mai usare compilatori cosi vecchi ???
ciao

 cartomanti ;-)
No, era:
1) Sbaglio io e quindi e' il mio codice che potra' dare problemi, magari
in futuro, quindi e' meglio che lo cambi e mi abitui a non usare piu'
"trucchi" simili, oppure sbaglia lui, quindi posso sperare che il mio
codice su compilatori piu' nuovi vada bene?
2) C'e' un modo per aggirare il problema? (A questo mi ha risposto Max).
3) Eventualmente un aggiornamento puo' risolvere? (ed a questo mi hai
risposto tu ora).
A livello pratico attribuire colpe o meriti mi serviva a ben poco :)
 bye
	av.

 come mai usare compilatori cosi vecchi ???
Pigrizia? :)
A parte gli scherzi: finche un prodotto funziona mi guardo bene
dall'aggiornarlo (e questo vale per i compilatori come per tutti gli
altri tipi di software). Se la versione nuova effettivamente corregge un
baco che ho riscontrato o introduce nuove funzioni *che mi servono*,
allora si, altrimenti squadra che vince non si cambia.
 bye
	av.

 allora si, altrimenti squadra che vince non si cambia.
Il gcc è sicuramente un compilatore non molto valido, ma d'altra parte
non è un prodotto commerciale e non potrebbe essere altrimenti.
La 3.3 ha probabilmente più difetti della 2.95, ma almeno ha una
conformità allo standard molto elevata, il che ti permette di scrivere
codice con maggiore libertà, potendo sfruttare (quasi) tutte le
caratteristiche che il C+ offre. Direi che, tutto sommato, ne vale la
pena.
Ciao.
-- 
BSD is for UNIX lovers -- linux is for Windows haters.

 Direi che, tutto sommato, ne vale la pena.
Dipende dai casi.
 bye
	av.

 av.
Hai qualche idea anche per l'operator =?
 bye & Thanks
	av.

 A livello pratico attribuire colpe o meriti mi serviva a ben poco :)
Io ho espresso solo il mio parere...
lo standard C++ e' uscito nel 98, il VC++6 e' del 97 non si puo' dire che
"come al solito" non e' conforme allo standard. Usa .NET 2003 e vedrai che
e' piu conforme. Lo stesso vale per il gcc del 97 e quello del 2003!
Tutto qua....
Tommy

il problema. L'interfaccia e' sempre quella.
non so cosa intendi per "interfaccia", ma una volta compilata una
libreria .dll o .lib, questa č utilizzabile da QUALSIASI bravo
compilatore... per dire, io uso librerie fatte con VC++ di qualsiasi
versione col mio Borland C++Builder!!
-- 
]\/[ad]\/[age (
www.mindreamz.net
)
"Un Maestro insegue semplicemente la sua visione di Eccellenza in
qualunque cosa sta facendo e lascia agli altri decidere se sta 
lavorando o giocando. A lui sembra sempre di fare entrambe." - Anonimo

Su questo non concordo. Perche' lo dici?
imho č un ottimo compilatore, a patto che tu non usi il 2.9x
(parlando di c++)
sbirish.

 versione col mio Borland C++Builder!!
Sinceramente non ho mai provato. C++ Builder non ce l'ho, non so se sia
un caso, se volutamente Borland abbia copiato la soluzione di MicroSoft,
se viceversa o se ci siano altri motivi.
 bye
	av.

 (parlando di c++)
Mi sto lentamente adeguando al 3.x, ma per ora non ho ancora risolto
alcuni problemi su alcune piattaforme (che sarebbero OT qui).
 bye & Thanks
	av.

se viceversa o se ci siano altri motivi.
non so di problemi di compatibilità in ambiente linux/unix... io
parlavo di librerie in ambiente windows, dll e lib... mi risulta
strano pensare che, mentre c++builder riesce a linkare librerie sia di
vc++ 6.0 che versioni precedenti, vc++ stesso non ci riesce...
ma è anche possibile che le librerie che uso io sono semplici, solo
funzioni e niente più, quindi niente template, niente classi... io
proverei, se non altro per il fatto che microsoft insiste in maniera
pedante a voler avere la compatibilità all'indietro di qualunque suo
prodotto


 Hai qualche idea anche per l'operator =?
Potresti fare cosě (sia per operator= che per il ctor):
 class X;
 struct X_leave_ownership_ref
 {
    X& reference;
    X_leave_ownership_ref( X& x );
 };
 class X
  {
   public:
    X( const X& x ) { /* ... */}
    X( X_leave_ownership_ref ) { /* ... */}
    void operator=( const X& x ) { /* ... */}
    void operator=( X_leave_ownership_ref ) { /* ... */}
 };
 X_leave_ownership_ref::X_leave_ownership_ref( X& x )
    : reference(x)
 {  reference.LeaveOwnerhip();
 }
 X_leave_ownership_ref  leave_ownership( X& x )
  { return X_leave_ownership_ref(x);}
    X a
    X b( a );
    X c( leave_ownership(b) );
    X d;
    X e;
    e = leave_ownership(d)
Max

ciao andrea,
forse arrivo tardi visto che e'gia'passato qualche giorno, 
in ogni caso......... per rendere la copia di oggetti efficiente, si
puo'usare un "reference counting".
L`esempio che ti scrivo e'tratto da un famoso libro sulla STL:
 template <class T>
   class CountedPtr {
     private:
       T* ptr;        // pointer to the value
       long* count;   // shared number of owners
     public:
       //initialize pointer with existing pointer
       //-requires that the pointer p is a return value of new
       explicit CountedPtr (T* p=0)
        : ptr(p), count(new long(1)) {
       }
       //copy pointer (one more owner)
       CountedPtr (const CountedPtr<T>& p) throw()
        : ptr(p.ptr), count(p.count) {
           ++*count;
       }
       //destructor (delete value if this was the last owner)
       ~CountedPtr () throw() {
           dispose();
       }
       //assignment (unshare old and share new value)
       CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
           if (this != &p) {
               dispose();
               ptr = p.ptr;
               count = p.count;
               ++*count;
           }
           return *this;
       }
       //access the value to which the pointer refers
       T& operator*() const throw() {
           return *ptr;
       }
       T* operator->() const throw() {
           return ptr;
       }
     private:
       void dispose() {
           if (--*count == 0) {
                delete count;
                delete ptr;
           }
       }
   };
Il codice descrive uno "smart pointer" che usa la tecnica del reference
counting, per usarlo nel tuo codice dovrai scrivere una cosa del tipo:
CountedPtr<your_class> the_object = CountedPtr(new your_class);
e per farne una copia:
CountedPtr<your_class> another_object = the_object;
attenzione pero', adesso i due oggetti puntano agli stessi dati per cui
occhio a quando fai dei cambiamenti; la variabile di "stato" count tiene in
mente che due oggetti puntano agli stessi dati evitando la distruzione dei
dati allocati fino a quando count non va a zero.
Usare un puntatore inteligente inoltre riduce i rischi dell'allocazione di
memoria dinamica.
Se gli smart pointer ti incuriosiscono puoi dare un'occhiata alla libreria
BOOST. 
ciao 
                  f


 funzioni e niente più, quindi niente template, niente classi...
Ah beh, allora certamente hai compatibilita'.
Come scritto mella faq che ho riportato prima, il problema sono le
classi.
 bye
	av.

 Max
Buona l'idea, ma c'e' ancora un problema:
Supponiamo io abbia:
X funz(...) {...}
Ora voglio fare:
X a(funz(...)) //Nessun problema, ma non sfrutto l'ottimizzazione.
Prima facevo:
X a(funz(...).LeaveOwnership());
Ora dovrei fare:
X a(leave_ownership(funz(...)));
che non funziona, in quanto non posso creare l'oggetto leave_ownership,
in quanto non posso assegnare ad un reference non-const un rvalue.
 bye & Thanks
	av.

 ha scritto nel messaggio
news:3F1C1D12...@flashnet.it...
"Max M." wrote:
Ora dovrei fare:
X a(leave_ownership(funz(...)));
che non funziona, in quanto non posso creare l'oggetto leave_ownership,
in quanto non posso assegnare ad un reference non-const un rvalue.
------------
un lvalue l'ho studiato giusto oggi il capitolo delle const ... se sbaglio
ditemelo almeno capisco meglio e di piu' ...
ciao

 in quanto non posso assegnare ad un reference non-const un rvalue.
E già. Non ho una soluzione, però ho un suggerimento.
La copia di un container standard o di un std::string può essere molto
costosa. Queste classi, però, sono dotate della funzione swap() che ha
complessità costante. Non potresti adottare questa approccio per offrire
all'utilizzatore quel tipo di ottimizzazione?
Max


Beh, a questo punto credo ci siano soluzioni comunque piu' semplici,
tipo un metodo, che so, Copy che faccia quello che farebbe operator = in
caso di trasferimento di possesso.
 bye & Thanks
	av.

 ditemelo almeno capisco meglio e di piu' ...
Scusa? Non ho capito cosa vuoi dire.
 bye
	av.

 ha scritto nel messaggio
news:3F1C43D1...@flashnet.it...

che l'errore della definizione const non viene generato sul rvalue ma sull'
lvalue, cioe' non puoi usare il ritorno costante come ingresso ad una
funzione che non ha il passaggio della variabile costante ... cosi hai
capito ?


Vagamente.
Il problema e' che, nel caso di cui si parlava, al costruttore
Xref::Xref(X&x);
viene passato il risultato della funzione:
X y(...);
Ora, y(...) restituisce un rvalue, ovvero, a grandi linee, un oggetto al
quale non puoi assegnare un valore, se Xref::Xref(X&x) accettasse tale
valore, poi potrebbe, non essendoci piu' traccia del fatto che era un
rvalue, assegnargli qualcosa. Es:
Xref::Xref(X&x)
{
  x=...;
}
L'istruzione di cui sopra, equivarrebbe a questo punto a:
y(...)=...;
che ovviamente non e' ammesso.
A sinistra di = ci deve stare un lvalue (l sta per left).
A destra di = ci deve stare un rvalue (r sta per right), tenendo
presente che un lvalue puo' essere trattato come un rvalue, quindi anche
un lvalue va bene.
 bye
	av.

 in giro, secondo me.
Sė, ci sono ottimi prodotti non commerciali, ma sono l'eccezione, non la
regola.


 che ovviamente non e' ammesso.
Uhm. Prova questo:
 std::string get_string()  { return ""; }
 get_string() = "ciao";
Max

Ho percio realizzato un meccanismo di trasferimento per possesso:
ma non era più semplice usare un auto_ptr?
Ciao.


Hai ragione, ho generalizzato troppo. Vediamo se interpreto
correttamente:
_ il draft dello standard specifica (3.10:5) che il risultato di una
funzione che non restituisce un reference e' un rvalue, quindi y() e
get_string() restituiscono degli rvalue;
_ ancora al punto 3.10:4 si dice (in un esempio, che quindi non e'
proprio parte dello standard): gli operatore di assegnazione predefiniti
si aspettano a sinistra un lvalue;
_ quindi get_string()="ciao" funziona in quanto non usa un operator =
predefinito; allo stesso modo funzionera' y(...)=... se per la classe X
di cui si parlava definisco un operatore di assegnazione; non ho provato
se basterebbe eventualmente quello definito implicitamente dal
compilatore;
_ per essere piu' preciso, quando ho scritto "un lvalue puo' essere
trattato come un rvalue", era da intendersi: quando compare un lvalue
dove e' richiesto un rvalue, l'lvalue e' convertito in un rvalue
(3.10:7);
_ ancora, al punto 3.10:10 si dice: e' necessario un lvalue per poter
modificare un oggetto, con l'eccezione che un rvalue di tipo class puo'
essere modificato in determinate circostanze (es: invocando un suo
metodo), il che mi consentiva di fare quel funz(...).LeaveOwnership()
citato in un messaggio precedente;
_ tornando a leave_ownership del tuo esempio: (punto 8.5.3:5,6,8) un
reference ... deve essere inizializzato con ... un lvalue ... oppure
deve essere di tipo non-volatile const (in questo caso, tra l'altro e'
permesso al compilatore la creazione di un oggetto temporaneo), quindi
ecco perche' non puo' funzionare leave_ownership(funz(...)).
A questo punto, ti chiederei il tuo parere sul codice che avevo scritto.
Riassumo:
class X
{
  public:
...
    X(X&);
    X(const X&);
    X&LeaveOwnership();
};
X y(...);
Facendo:
X z;
z=y(...).LeaveOwnership();
viene chiamato X(X&) (ho provato a seguirlo in debugging).
Mentre
z=y(...)
chiama X(const X&).
Ragionandoci sopra:
nel secondo caso: y(...) e' un rvalue, quindi non puo' essere assegnato
al parametro di X(X&) che e' non const.
Puo' pero' essere modificato con LeaveOwnership(). Quest'ultima
restituisce un X&, che essendo un reference e' un lvalue e puo' allora
essere chiamato X(X&), perche' l'lvalue puo' essere assegnato al
parametro.
Allora avrei anche trovato la soluzione per implementare il tuo ultimo
suggerimento: basterebbe un metodo:
X&rvaluetolvalue() {return *this;}
ed ecco che aggirerei l'impossibilita' di fare:
X a(leave_ownership(funz(...)))
con un bel
X a(leave_ownership(funz(...).rvaluetolvalue()))
Provato e compila (non l'ho ancora eseguito).
Eppure, non so, la cosa mi perplede un po': sono abituato a pensare che
in C++ tutto sia possibile, che basti usare un po' di fantasia per
aggirare i limiti semantici e fregare lo standard, pero' mi viene sempre
da pensare se non ci siano controindicazioni.
Ovvero, e' ovvio che un uso indiscriminato di tale metodo puo' portare a
fare un reference ad un temporaneo che potrebbe essere distrutto prima
del reference stesso. Certo, chi usa rvaluetolvalue, probabilmente sa
cosa sta facendo; potrei anche aggiungere un commento del tipo "da usare
a vostro rischio e pericolo, ecc...".
Insomma, secondo me posso farlo, pero' prenderei in considerazione
volentieri anche un parere altrui.
 bye
	av.


Premetto che preferisco limitare l'uso di auto_ptr a casi molto
semplici, in quanto, come detto prima, sono costretto dal committente ad
usare un compilatore vecchio e la definizione di auto_ptr da allora e'
cambiata.
Cio' detto, in che modo potrei usarlo?
Intendi all'interno della classe?
Se cosi', non credo vada bene, perche', in generale, ho bisogno di
copiare gli oggetti duplicandone il contenuto. Il trasferimento di
possesso e' un meccanismo di ottimizzazione che viene usato in un numero
limitato di casi.
 bye & Thanks
	av.

limitato di casi.
In questo caso non devi fare altro che trasmettere l'ownership al nuovo
oggetto. con la comodità di non dover costruire artifici per la distruzione
della memoria.
ciao,
Maurizio.

 con la comodità di non dover costruire artifici per la distruzione della memoria.
Sigh! All'interno della classe sfrutto una libreria C che ha definito a
modo suo la gestione della memoria, con il suo garbage collection, le
sue funzioni per allocarla, ecc...
Temo non sia cosi' facile usare un auto_ptr.
 bye & Thanks
	av.

...

Credo che costruttore di copia ed assegnamento in auto_ptr non siano
definiti come in questo esempio.
Il vantaggio di questo smart pointer e' la possibilita' di rendre la
copia piu' efficiente.