Mise à JOUR au bas
q1: Comment voulez-vous mettre en œuvre la règle de cinq pour une classe qui gère plutôt que des ressources importantes, mais de qui vous voulez être passés par valeur, car qui simplifie grandement et embellit son utilisation? Ou ne sont pas toutes les cinq éléments de la règle, même nécessaire?
Dans la pratique, je commence quelque chose avec l'imagerie 3D où une image est généralement 128*128*128 en double. Pouvoir bien écrire des choses comme cela rendrait les calculs beaucoup plus facile:
Data a = MakeData();
Data c = 5 * a + ( 1 + MakeMoreData() ) / 3;
q2: à l'Aide d'une combinaison de copie élision / RVO / sémantique de déplacement, le compilateur doit être en mesure de de ce de ce avec un minimum de la copie, non?
J'ai essayé de comprendre comment ce faire j'ai donc commencé avec les bases; supposons qu'un objet de la mise en œuvre de la manière traditionnelle de mise en œuvre de la copie et de l'affectation:
class AnObject
{
public:
AnObject( size_t n = 0 ) :
n( n ),
a( new int[ n ] )
{}
AnObject( const AnObject& rh ) :
n( rh.n ),
a( new int[ rh.n ] )
{
std::copy( rh.a, rh.a + n, a );
}
AnObject& operator = ( AnObject rh )
{
swap( *this, rh );
return *this;
}
friend void swap( AnObject& first, AnObject& second )
{
std::swap( first.n, second.n );
std::swap( first.a, second.a );
}
~AnObject()
{
delete [] a;
}
private:
size_t n;
int* a;
};
Maintenant, entrez rvalues et la sémantique de déplacement. Aussi loin que je peux dire que ce serait un travail de mise en œuvre:
AnObject( AnObject&& rh ) :
n( rh.n ),
a( rh.a )
{
rh.n = 0;
rh.a = nullptr;
}
AnObject& operator = ( AnObject&& rh )
{
n = rh.n;
a = rh.a;
rh.n = 0;
rh.a = nullptr;
return *this;
}
Cependant, le compilateur (VC++ 2010 SP1) n'est pas trop heureux avec cela, et les compilateurs sont généralement correctes:
AnObject make()
{
return AnObject();
}
int main()
{
AnObject a;
a = make(); //error C2593: 'operator =' is ambiguous
}
q3: Comment résoudre ce problème? Revenir à AnObject& operator = ( const AnObject& rh ) corrige certainement, mais ne pas nous perdre une assez grande optimisation de l'occasion?
En dehors de cela, il est clair que le code du constructeur de déplacement et de cession est plein de duplication. Donc pour l'instant on oublie l'ambiguïté et essayer de le résoudre à l'aide de la copie et de swap, mais maintenant pour les rvalues. Comme expliqué ici, nous n'aurions même pas besoin d'un custom swap, mais plutôt avoir std::swap de faire tout le travail, ce qui semble très prometteur. J'ai donc écrit la suite, en espérant std::swap copie de construire un temporaire en utilisant le constructeur de déplacement, puis remplacez-la par *ce:
AnObject& operator = ( AnObject&& rh )
{
std::swap( *this, rh );
return *this;
}
Mais qui ne fonctionne pas et au lieu de cela conduit à un débordement de la pile grâce à une récursion infinie depuis std::swap appelle notre opérateur = ( AnObject&& rh ) à nouveau. q4: quelqu'un Peut-il donner un exemple de ce que l'on entend dans l'exemple alors?
Nous pouvons résoudre ce problème en fournissant une deuxième fonction d'échange:
AnObject( AnObject&& rh )
{
swap( *this, std::move( rh ) );
}
AnObject& operator = ( AnObject&& rh )
{
swap( *this, std::move( rh ) );
return *this;
}
friend void swap( AnObject& first, AnObject&& second )
{
first.n = second.n;
first.a = second.a;
second.n = 0;
second.a = nullptr;
}
Il y a maintenant presque deux fois la quantité de code, cependant, le transfert d'une partie de paie de par alowing assez bon marché en mouvement; mais d'autre part, l'attribution normale ne peut pas bénéficier de la copie élision plus. À ce point, je suis vraiment confus mais, et ne voyant pas plus ce qui est bien et de mal, donc je suis l'espoir d'obtenir quelques commentaires ici..
Mise à JOUR de Sorte qu'il semble qu'il y a deux camps:
- l'un disant à ignorer l'opérateur d'assignation de déplacement et de continuer à faire ce que le C++03 nous a enseigné, c'est à dire écrire un seul opérateur d'affectation qui passe l'argument par valeur.
- l'autre en disant à mettre en œuvre l'opérateur d'assignation de déplacement (après tout, c'est du C++11) et d'avoir la copie opérateur d'affectation de prendre son argument par référence.
(ok et il y a le 3ème camp de me dire d'utiliser un vecteur, mais c'est une sorte de hors de la portée de cet hypothétique classe. Ok dans la vraie vie, je voudrais utiliser un vecteur, et il y aurait aussi d'autres membres, mais depuis le constructeur de déplacement/d'affectation ne sont pas générées automatiquement (encore?) la question serait toujours en attente)
Malheureusement, je ne peut pas tester les deux mises en œuvre dans un scénario réel, puisque ce projet vient de commencer et la façon dont les données seront effectivement flux n'est pas encore connu. J'ai donc tout simplement mis en œuvre à la fois d'entre eux, ajouté des compteurs pour l'allocation etc et a couru un couple d'itérations de l'env. ce code, où T est l'une des implémentations:
template< class T >
T make() { return T( narraySize ); }
template< class T >
void assign( T& r ) { r = make< T >(); }
template< class T >
void Test()
{
T a;
T b;
for( size_t i = 0 ; i < numIter ; ++i )
{
assign( a );
assign( b );
T d( a );
T e( b );
T f( make< T >() );
T g( make< T >() + make< T >() );
}
}
Ce code n'est pas assez bon pour tester ce que je suis après, ou le compilateur est juste trop intelligent: n'a pas d'importance ce que j'utilise pour arraySize et numIter, les résultats pour les deux camps sont à peu près identiques: même nombre d'allocations, de très légères variations dans le calendrier, mais pas reproduit de différence significative.
Donc à moins que quelqu'un peut pointer vers une meilleure façon de tester cela (étant donné que l'utilisation réelle scnearios ne sont pas encore connus), je vais devoir conclure qu'il n'a pas d'importance et est donc laissée à la goût de le developper. Dans ce cas, je choisirais #2.