【问题标题】:const correct composition using std::unique_ptr / std::shared_ptr使用 std::unique_ptr / std::shared_ptr 的 const 正确组合
【发布时间】:2018-03-06 01:54:47
【问题描述】:

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

以下示例允许更改 my_foo 拥有的内容,尽管它是 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;
}

乍一看,将std::make_unique&lt;T&gt; 替换为std::make_unique&lt;const T&gt; 似乎是一个不错的解决方案。但是,这不允许更改 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 可以拥有一个指向多态的基类的指针,即我们无法实现的对象只需按值存储。

那么如何更好地处理这种情况呢?

【问题讨论】:

  • 我不知道您为什么允许在 const foo 对象上调用 increment?根据名称,在这种情况下不应该被调用
  • 我想与您使用原始指针所做的类似。某种包装类。
  • 似乎是 std::experimental::propagate_const 的用途。但是我没有足够的知识来写答案。
  • @UnholySheep 对我来说问题是,我首先被允许使用const 说明符写increment
  • @NickyC,是的,看起来std::experimental::propagate_const 正是我要找的。似乎我必须等待相当长的时间才能使用它。 ;-)

标签: c++ pointers c++14 constants const-correctness


【解决方案1】:

您可以继承 std::unique_ptr 并仅覆盖 3 个(unique_ptr&lt;T[]&gt; 为 4 个)方法,提供 const/non-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();
    }
}; 

【讨论】:

  • 非常感谢。您的解决方案效果很好,我喜欢它,因为模式被抽象掉了,实际的类foo 一定不能修改很多。 :) test1 test2 test3
【解决方案2】:

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

类似这样的:

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

【讨论】:

    猜你喜欢
    • 2013-05-05
    • 2022-08-04
    • 2016-08-03
    • 1970-01-01
    • 2015-07-23
    • 2021-08-28
    • 2019-10-20
    • 2018-02-02
    • 2017-08-13
    相关资源
    最近更新 更多