2010-07-19 2 views
3

J'ai une classe qui doit contenir une référence à certaines données, sans posséder ces données (c'est-à-dire que les données réelles sont garanties de ne pas sortir du cadre). En particulier, la classe ne peut pas faire une copie - les données sont facilement de plusieurs gigaoctets.Titulaire non détenteur de sémantique d'affectation

Maintenant, la mise en œuvre habituelle (je suppose) est d'avoir une référence aux données:

struct holder_ref { 
    type const& value; 

    holder_ref(type const& value) : value(value) { } 
}; 

(S'il vous plaît noter que la RNS const n'a absolument aucune incidence sur le problème). Maintenant, j'ai absolument besoin que cette classe soit assignable (c'est-à-dire qu'elle ait un operator = fonctionnel). Je pensais que c'était un problème assez commun, mais je ne me souviens pas comment (si jamais) je l'ai résolu avant.

Le problème est que une référence ne peut pas être affectée et il n'y a tout simplement pas moyen de contourner cela. La seule solution que je suis venu avec des utilisations nouvelles de placement en place de l'opérateur d'affectation:

// x = other_x; gets replaced with: 
x.~T(); 
new (&x) T(other_x); 

Maintenant, cela fonctionne et est conforme à la norme. Mais c'est sûr que c'est moche. Non - inacceptable.

Alors je cherche des alternatives. Une idée est d'utiliser des pointeurs, mais je ne suis pas certain que mon constructeur est effectivement garanti au travail (et le passage d'un pointeur est impossible en raison de l'interface que je dois adhérer à):

struct holder_ptr { 
    type const* value; 

    // Is this legal? 
    holder_ptr(type const& value = 0) : value(&value) { } 
}; 

Mais je préfère utiliser une référence, si possible. Seulement - comment implémenter l'opérateur d'affectation?

struct holder_ref { 
    type const& value; 

    holder_ref(type const& value = 0) : value(value) { } 

    holder_ref& operator =(holder_ref const& other) { 
     // Now what?! 
     return *this; 
    } 
}; 

En cas de test, considérez le code suivant:

int main() { 
    int const TEST1 = 23; 
    int const TEST2 = 13; 
    int const TEST3 = 42; 
    std::vector<holder_ptr> hptr(1); 
    std::vector<holder_ref> href(2); 

    // Variant 1. Pointer. 
    hptr[0] = holder_ptr(TEST1); 

    // Variant 2. Placement new. 
    href[0].~holder_ref(); 
    new (&href[0]) holder_ref(TEST2); 

    // Variant 3. ??? 
    href[1] = holder_ref(TEST3); 

    assert(*hptr[0].value == TEST1); // Works (?) 
    assert(href[0].value == TEST2); // Works 
    assert(href[1].value == TEST3); // BOOM! 
} 

(Aussi, pour que cela soit clair - le type dont nous parlons est non-POD et je besoin d'un conforme à la norme solution.)

+3

Quel est le problème avec la solution de pointeur? Cela semble correspondre parfaitement à votre cas d'utilisation. C'est un membre assignable et il n'exprime pas ou n'implique pas la propriété. –

+0

Bien que vous ne puissiez pas stocker légitimement un pointeur sur un temporaire, votre valeur par défaut pour la valeur: 'holder_ptr (type const & value = 0)' est imprudente. (Mais cela aurait été aussi valable pour les références.) –

+0

@Charles: oui, c'est un autre problème que la solution de référence a. –

Répondre

6

Je ne vois rien de mal à utiliser un holder_ptr. Il peut être mis en œuvre quelque chose comme ceci:

struct bad_holder : std::exception { }; 

struct holder_ptr { 
    holder_ptr() : value(0) { } 
    holder_ptr(type const& value) : value(&value) { } 

    type const& get() { 
     if (value == 0) throw bad_holder(); 
     return *value; 
    } 
private: 
    type const* value; 
}; 

Tant que vous attribuez toujours le pointeur d'une référence, vous savez que vous avez un objet valide (qui, ou vous vous retrouviez avec une « référence null » précédemment , auquel cas vous avez d'autres problèmes plus importants puisque vous avez déjà invoqué un comportement indéfini).

Avec cette solution, l'interface est entièrement implémentée en termes de références, mais sous le capot un pointeur est utilisé pour que le type soit assignable. L'utilisation de références dans l'interface garantit qu'il n'y a aucune des préoccupations qui viennent avec l'utilisation de pointeurs (à savoir, vous ne devez jamais vous inquiéter si le pointeur est nul).

Édition: J'ai mis à jour l'exemple pour permettre au support d'être constructible par défaut.

+0

Vous pouvez également implémenter ceci comme une sorte de wrapper de pointeur et surcharger '*' et '->' au lieu d'utiliser 'get()' ... –

1

Une norme TR1 weak_ptr est-elle suffisamment conforme?

+0

Oui - malheureusement, TR1 est sorti pour le projet, tout comme Boost (je sais, je sais, stupide ...). Je n'ai même pas pensé à ça. –

+2

'weak_ptr' n'est valide que si quelqu'un possède les données avec un' shared_ptr'. Même si c'est le cas, c'est un peu inutile étant donné que "les données réelles sont garanties de ne pas sortir du cadre". –

3

J'utiliserais le support de pointeur.Mais si vous êtes absolument contre cela, que diriez-vous cacher votre placement new operator=:

holder_ref& operator =(holder_ref const& other) { 
    new (this) holder_ref(other); 
    return *this; 
} 
+0

Ooh, j'ai oublié cette solution. Beau (par la norme C++)! (Seulement, il a besoin de l'appel du destructeur auparavant.) –

+2

Il a également besoin de protection contre l'auto-affectation. Comme Herb Sutter, je n'aime vraiment pas cette technique; c'est très mauvais si quelqu'un dérive de holder_ref (peut-être improbable). Aussi, voir ici: http://www.gotw.ca/gotw/023.htm –

+0

@Charles: pour être juste, les points soulevés dans GotW # 23 ne s'appliquent pas ici (et je peux * garantir * cela, en raison des contraintes de la bibliothèque), sauf pour les points 6 et 7 qui, selon moi, sont des erreurs de la norme (c'est-à-dire techniquement vraies à la lettre mais probablement involontaires et universellement comprises différemment), et qui ne sont pas pertinentes en pratique, et pour le point 4, qui est un bummer. –

Questions connexes