mai
2010
Après avoir abordé static_assert dans mon dernier billet, passons à la référence R-Value.
Pour bien comprendre ce qui va suivre il est impératif de connaitre les méthodes de transmission d’un argument dans une méthode en l’occurrence le passage par valeur et par référence.
Dans la littérature C++, rValue désigne un objet pouvant figurer à droite (right) d’une affectation, alors que lValue désigne un objet pouvant se trouver à gauche.
Concrètement une nouvelle forme de déclaration utilisant l’opérateur && va nous permettre de transférer la responsabilité d’une ressource d’un objet temporaire à un autre, évitant ainsi une libération mémoire et une nouvelle allocation et la copie de la ressource concernée.
Voici une petite classe d’exemple qui illustre le sujet :
{
public:
XDate():m_szDate(nullptr){}
~XDate() {delete [] m_szDate;}
XDate(const char *szDate):m_szDate(nullptr)
{
SetDate(szDate);
}
XDate(const XDate &date):m_szDate(nullptr)
{
SetDate(date.m_szDate);
}
void SetDate(const char *szDate)
{
if(m_szDate==nullptr) m_szDate= new char [7];
strcpy_s(m_szDate,7,szDate);
}
const XDate& operator=(const XDate& Src)
{
if(&Src==this) return *this;
SetDate(Src.m_szDate);
return *this;
}
const char *c_str(){return m_szDate;}
void DebugTrace()
{
TRACE("\nDate:%04X ->(%s)",m_szDate,m_szDate==nullptr?"":m_szDate);
}
public:
char *m_szDate;
};
XDate GetCurrentDay()
{
return XDate("030510");
}
void SetDate(const XDate& date)
{
XDate d(date);
d.DebugTrace();
}
int main()
{
SetDate( GetCurrentDay());
}
XDate alloue en mémoire une chaine de caractère pour tenir une date.
Dans l’appel réalisé dans le main on retrouve bien la séquence décrite précédemment ou un objet temporaire fournit par GetCurrentDay() sert affecter un objet de la même classe dans la méthode SetDate.
Le nouvel opérateur && va nous permettre de faire l’économie d’une libération, allocation et d’un transfert mémoire :
Je vais modifier pour l’exemple la fonction SetDate comme suit :
{
XDate d;
d.m_szDate=date.m_szDate;
date.m_szDate=nullptr;
d.DebugTrace();
}
Dans SetDate je récupère directement l’adresse contenue par le pointeur de l’objet temporaire et je positionne à nullptr celui de l’objet temporaire.
La méthode SetDate modifiée a été appelée parce que nous bien en présence d’un objet temporaire fournit par GetCurrentDay().
Si je modifie comme suit la fonction main() :
{
XDate d("030510");
SetDate(d);
}
J’obtiens l’erreur:
You cannot bind an lvalue to an rvalue reference
Note: IntelliSense m’avait déjà prévenu de l’erreur avant la compilation …
Si on veut avoir un code opérationnel dans les deux cas, il faudra laisser l’autre méthode SetDate active :
{
XDate d(date);
d.DebugTrace();
}
void SetDate(XDate&& date)
{
XDate d;
d.m_szDate=date.m_szDate;
date.m_szDate=nullptr;
d.DebugTrace();
}
Si on veut forcer la main au compilateur pour utiliser la méthode avec la déclaration rValue on pourra utiliser un static_cast :
{
XDate d("030510");
SetDate(static_cast<XDate &&>(d));
d.DebugTrace();
}
Autre exemple :
{
XDate d("030510");
SetDate(d);
SetDate(XDate("030510"));
}
le premier SetDate reçoit une variable locale (lValue) et donc appellera la version const de SetDate.
le deuxiéme appel est réalisé avec un objet temporaire car référencé nul part dans le programme, c’est donc bien un rValue qui appellera la version XDate && de SetDate.
Bien, maintenant que nous avons compris comment tirer parti de cette fonctionnalité, il reste à implémenter ce mécanisme directement dans notre classe en ajoutant un constructeur de déplacement (Move constructor ) et l’opérateur d’affectation (Move assignement) ainsi qu’un opérateur +.
La classe finale :
{
public:
XDate():m_szDate(nullptr){}
~XDate() {delete [] m_szDate;}
XDate(const char *szDate):m_szDate(nullptr)
{
SetDate(szDate);
}
XDate(XDate &&date):m_szDate(nullptr)
{
Move(date);
TRACE("\nMC:Date:%04X ->(%s)",m_szDate,m_szDate==nullptr?"":m_szDate);
}
XDate(const XDate &date):m_szDate(nullptr)
{
SetDate(date.m_szDate);
}
void SetDate(const char *szDate)
{
if(m_szDate==nullptr) m_szDate= new char [7];
strcpy_s(m_szDate,7,szDate);
DebugTrace();
}
XDate& operator=(XDate&& Src)
{
if(&Src==this) return *this;
Move(Src);
TRACE("\nMA:Date:%04X ->(%s)",m_szDate,m_szDate==nullptr?"":m_szDate);
return *this;
}
friend XDate operator+(XDate&& left, const int nDayRight);
const XDate& operator=(const XDate& Src)
{
if(&Src==this) return *this;
SetDate(Src.m_szDate);
return *this;
}
const char *c_str(){return m_szDate;}
void Move(XDate &date)
{
m_szDate=date.m_szDate;
date.m_szDate=nullptr;
}
void DebugTrace()
{
TRACE("\nDate:%04X ->(%s)",m_szDate,m_szDate==nullptr?"":m_szDate);
}
public:
char *m_szDate;
};
XDate operator+(XDate && left, const int nDayRight)
{
XDate date;
date.Move(left);
TRACE("\nM+:Date:%04X ->(%s)",date.c_str(),date.c_str());
// operation d’ajout du jour non implementée...
return date;
}
Et l’utilisation :
d= GetCurrentDay()+1;
d.DebugTrace();
la première ligne utilise le constructeur de copie normal et pas le constructeur de déplacement comme on pourrait s’y attendre (ce qui ressemble bien à un bug (?)).
La seconde ligne va utiliser successivement l’opérateur +, le constructeur de déplacement et enfin l’opérateur d’affectation de déplacement.
Voila pour ce billet un peu long qui j’espère vous a éclairé sur l’utilisation de la référence R-Value.
Référence MSDN
Articles récents
Commentaires récents
Archives
- septembre 2010
- mai 2010
- avril 2010
- février 2010
- août 2009
- juin 2009
- mai 2009
- février 2009
- décembre 2008
- novembre 2008
- octobre 2008
- septembre 2008
- juillet 2008
- juin 2008
- mai 2008
- avril 2008
- mars 2008
- février 2008
- janvier 2008
- décembre 2007
- novembre 2007
- septembre 2007
- août 2007
- juillet 2007
- juin 2007
- mai 2007
- avril 2007
- mars 2007
- février 2007
- janvier 2007
- décembre 2006
- novembre 2006
- octobre 2006
- septembre 2006
- août 2006
- juillet 2006
- juin 2006
- mai 2006
- mars 2006
- février 2006