2017-09-25 116 views
2

目前我想知道如何正确使用std::unique_ptr作为const正确性的成员变量。const使用std :: unique_ptr/std :: shared_ptr正确组合

下面的例子允许改变由my_foo所拥有的内容,尽管正在常量:

#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; 
} 

更换std::make_unique<T>std::make_unique<const T>好像乍一看很好的解决方案。然而,这不允许改变my_foo内容,即使它是非常量:

#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; 
} 

拥有一个像这个小例子,当然不是非常有意义的指针为int,但在实际的代码unique_ptr把持不住一个指向多态的基类的指针,即一个我们无法简单地按值存储的对象。

那么这种情况怎么处理得更好呢?

+1

我不知道为什么你允许在'const foo'对象上调用'increment'吗?基于这个名字,不应该被允许在这种情况下被调用 – UnholySheep

+0

我想象的类似于你用原始指针做什么。某种包装类。 – juanchopanza

+3

似乎是[std :: experimental :: propagate_const](http://en.cppreference.com/w/cpp/experimental/propagate_const)的用途。但是我没有足够的知识来写答案。 –

回答

2

可以继承std::unique_ptr并覆盖只有3(4 unique_ptr<T[]>)方法,提供常量/非const重载:

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

非常感谢。您的解决方案效果很好,我喜欢它,因为该模式已被抽象出来,实际的类“foo”不能被修改很多。 :) [test1](https://ideone.com/pY0NlE)[test2](https://ideone.com/YRN1Lb)[test3](https://ideone.com/FKYBhT) –

1

我这样做的方式是通过提供一个内部协议来提供对底层实现的正确引用的引用的访问。

事情是这样的:

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_; 
}; 
相关问题