|
Dr. Carlo Pescio Return Value Optimization, Named Value Optimization e Costruttori Operazionali |
const int SIZE = 500 ;
class Vector
{
public :
Vector()
{}
Vector f( int x ) ;
private :
int v[ SIZE ] ;
} ;
Notiamo che la funzione Vector::f restituisce un (nuovo) oggetto Vector per valore. La gestione tradizionale (non ottimizzata) di una funzione come f da parte del compilatore e' la seguente:
// Versione originale
Vector Vector :: f( int x )
{
Vector l ;
// ...
// fa qualcosa su l
// ...
return( l ) ;
}
// versione trasformata dal compilatore
void Vector :: f( int x, Vector& _result )
{
Vector l ;
// ...
// fa qualcosa su l
// ...
// applica costruttore di copia (pseudo C++):
_result.Vector::Vector( l ) ;
return ;
}
2) le chiamate ad f vengono trasformate per allinearle alla versione "interna" di f:
// chiamata originale Vector v ; Vector g = v.f( 3 ) ; // versione trasformata, in pseudo-C++ Vector v ; // questo chiama il costruttore di default Vector g ; // gestione speciale: NO costruttore di default v.f( 3, g ) ;Osserviamo che per l'oggetto g NON viene chiamato il costruttore di default, in quanto il suo contenuto verra' comunque sovrascritto all'interno della funzione f.
// versione iniziale
Vector Vector :: f( int x )
{
Vector l ;
// ... inizializza l con tutti 1
return( *this + l ) ;
}
notiamo che f restituisce un oggetto anonimo, ovvero non legato ad alcun identificatore. In questo caso f verra' trasformata (secondo l'approccio "classico" di cui sopra) in questa versione:
// versione "standard" trasformata dal compilatore
void Vector :: f( int x, Vector& _result )
{
Vector l ;
// ... inizializza l con tutti 1
Vector tmp ; // NO costruttore di default
Vector::operator+( *this, l, tmp ) ;
// applica costruttore di copia (pseudo C++):
_result.Vector::Vector( tmp ) ;
return ;
}
tuttavia il compilatore puo' facilmente eliminare l'oggetto tmp e il costruttore di copia da tmp in _result, semplicemente mettendo il risultato direttamente dentro _result:
// versione ottimizzata
void Vector :: f( int x, Vector& _result )
{
Vector l ;
// ... inizializza l con tutti 1
Vector::operator+( *this, l, _result ) ;
return ;
}
Questa ottimizzazione viene detta Return Value Optimization (RVO) e si applica solo quando l'oggetto restituito e' anonimo. Un modo per ottenere un oggetto anonimo e' di "costruirlo al volo" nello statement return. Cio' significa (ad esempio) che usando un compilatore che implementa la (sola) RVO e' meglio scrivere codice come questo:
Vector Vector :: operator +( const Vector& r )
{
return( Vector( *this ) += r ) ;
}
Vector Vector :: operator +( const Vector& r )
{
Vector l = *this ;
l += r ;
return( l ) ;
}
Vector Vector :: operator +( const Vector& r )
{
Vector l ;
// ... loop sugli elementi, assegna
// a l[i] la somma di *this[i] e r[i]
return( l ) ;
}
// versione iniziale
Vector Vector :: f( int x )
{
Vector l ;
l.v[ 0 ] = v[ 0 ] + x ;
l.v[ 499 ] = v[ 499 ] + x ;
return( l ) ;
}
// versione "standard" trasformata dal compilatore
void Vector :: f( int x, Vector& _result )
{
Vector l ; // qui chiama costruttore di default
l.v[ 0 ] = v[ 0 ] + x ;
l.v[ 499 ] = v[ 499 ] + x ;
// applica costruttore di copia
// (pseudo C++):
_result.Vector::Vector( l ) ;
return ;
}
// Named Value Optimization
void Vector :: f( int x, Vector& _result )
{
// applica costruttore di default
// a _result (pseudo C++):
_result.Vector::Vector() ;
_result.v[ 0 ] = v[ 0 ] + x ;
_result.v[ 499 ] = v[ 499 ] + x ;
return ;
}
Notiamo che il costruttore di copia e' sparito, e che il costruttore di default ora viene chiamato su _result anziche' su l come avveniva nel listato caso precedente.
class Vector
{
public :
Vector() {}
Vector f( int x ) ;
private :
int v[ 500 ] ;
// costruttore operazionale:
Vector( const Vector& r, int x ) ;
} ;
Vector Vector :: f( int x )
{
// chiama direttamente
// il costruttore operazionale
return( Vector( *this, x ) ) ;
}
Vector :: Vector( const Vector& r, int x )
{
// fa quello che faceva f
// nei listati precedenti:
v[ 0 ] = r.v[ 0 ] + x ;
v[ 499 ] = r.v[ 499 ] + x ;
}
L'idea di base e' molto semplice: introdurre un costruttore ad-hoc, mantenendolo privato, che svolge gli stessi compiti che erano originariamente svolti da f. A questo punto, f puo' restituire un oggetto anonimo, facendo intervenire la RVO. Volendo, f puo' anche diventare una funzione inline, dal momento che il suo corpo si riduce ad un semplice return.
Vector Vector :: g( int x )
{
Vector l ;
l.v[ 0 ] = v[ 0 ] * x ;
return( l ) ;
}
ConclusioniBibliografia
[1] Carlo Pescio, "Ottimizzazioni e C++", Computer Programming No. 47.
Biografia
Carlo Pescio (pescio@eptacom.net) svolge attività di consulenza, progettazione e formazione in ambito internazionale. Ha svolto la funzione di Software Architect in grandi progetti per importanti aziende europee e statunitensi. È autore di numerosi articoli su temi di ingegneria del software, programmazione C++ e tecnologia ad oggetti, apparsi sui principali periodici statunitensi, nonché dei testi "C++ Manuale di Stile" ed "UML Manuale di Stile". Laureato in Scienze dell'Informazione, è membro dell'ACM, dell'IEEE e dell'IEEE Technical Council on Software Engineering.