2017-09-25 2 views
2

Actuellement, je me demande comment utiliser correctement un std::unique_ptr en tant que variable membre concernant l'exactitude const.const correcte composition en utilisant std :: unique_ptr/std :: shared_ptr

L'exemple suivant permet de modifier le contenu appartenant à my_foo en dépit est d'être const:

#include <iostream> 
#include <memory> 

struct foo { 
    foo() : value_ptr_(std::make_unique<int>(3)) {} 
    void increment() const { 
     ++(*value_ptr_); 
    } 
    int get_value() const { 
     return *value_ptr_; 
    } 
    std::unique_ptr<int> value_ptr_; 
}; 

int main() { 
    const foo my_foo; 
    std::cout << my_foo.get_value() << std::endl; 
    my_foo.increment(); // But my_foo is const! 
    std::cout << my_foo.get_value() << std::endl; 
} 

Remplacement std::make_unique<T> avec std::make_unique<const T> semble être une bonne solution au premier coup d'œil. Toutefois, ce n'autorise pas à modifier le contenu de my_foo même si elle est non-const:

#include <iostream> 
#include <memory> 

struct foo { 
    foo() : value_ptr_(std::make_unique<int>(3)) {} 
    void increment() { 
     ++(*value_ptr_); 
    } 
    int get_value() const { 
     return *value_ptr_; 
    } 
    std::unique_ptr<const int> value_ptr_; 
}; 

int main() { 
    foo my_foo; 
    std::cout << my_foo.get_value() << std::endl; 
    my_foo.increment(); // compiler error 
    std::cout << my_foo.get_value() << std::endl; 
} 

Avoir un pointeur vers un int comme dans cet exemple minimal est bien sûr pas très significatif, mais dans le code réel du unique_ptr pourrait tenir un pointeur vers une classe de base de quelque chose de polymorphe, c'est-à-dire un objet que nous ne pourrions pas simplement stocker par valeur.

Alors, comment cette situation peut-elle être mieux gérée?

+1

Je ne sais pas pourquoi vous autorisez 'increment' à être appelé sur un objet' const foo'? Basé sur le nom, il ne devrait pas être autorisé à être appelé dans ce cas – UnholySheep

+0

J'imagine de la même manière que ce que vous feriez avec un pointeur brut. Une sorte de classe d'emballage. – juanchopanza

+3

Semble être ce que [std :: experimental :: propagate_const] (http://en.cppreference.com/w/cpp/experimental/propagate_const) est pour. Mais je n'ai pas assez de connaissances pour écrire une réponse. –

Répondre

2

vous pouvez hériter std::unique_ptr et passer outre à seulement 3 (4 pour unique_ptr<T[]>) méthodes, fournissant const/non const surcharges:

template <typename T> 
struct propagating_unique_ptr : std::unique_ptr<T> { 
    using unique_ptr<T>::unique_ptr; 
    using unique_ptr<T>::operator =; 

    const T *get() const noexcept { 
     return unique_ptr<T>::get(); 
    } 
    T *get() noexcept { 
     return unique_ptr<T>::get(); 
    } 

    const T &operator *() const noexcept { 
     return unique_ptr<T>::operator *(); 
    } 
    T &operator *() noexcept { 
     return unique_ptr<T>::operator *(); 
    } 

    const T *operator ->() const noexcept { 
     return unique_ptr<T>::get(); 
    } 
    T *operator ->() noexcept { 
     return unique_ptr<T>::get(); 
    } 
}; 
+0

Merci beaucoup. Votre solution fonctionne très bien et je l'aime parce que le motif est abstrait et la classe réelle «foo» ne doit pas être modifiée beaucoup. :) [test1] (https://ideone.com/pY0NlE) [test2] (https://ideone.com/YRN1Lb) [test3] (https://ideone.com/FKYBhT) –

1

La façon dont je le fais est en fournissant un protocole interne pour fournir un accès à la référence correctement définie à l'implémentation sous-jacente.

Quelque chose comme ceci:

struct foo { 
    // standard (in your codebase) protocol to express the impl base class 
    using underlying_impl = int; 

    // standard protocol to express ownership semantics 
    using implementation_handle = std::unique_ptr<underlying_impl>; 

    // construction via private 'construct' protocol  
    foo() : value_ptr_(construct(3)) {} 

    // all internal access to the implementation via a the protocol 
    // of get_impl() 
    auto operator++() -> foo& 
    { 
     // not-const - compiles fine 
     ++get_impl(); 
     return *this; 
    } 

    void increment() const { 
// now won't compile - get_impl() propagates const correctly 
//  ++get_impl(); 
    } 

private: 

    static auto construct(int val) -> implementation_handle 
    { 
     return std::make_unique<underlying_impl>(val); 
    } 

    // two versions of get_impl() - const and mutable 
    auto get_impl() const -> underlying_impl const& 
    { 
     return *value_ptr_; 
    } 

    auto get_impl() -> underlying_impl& 
    { 
     return *value_ptr_; 
    } 

    // actual storage of the implementation handle 
    implementation_handle value_ptr_; 
};