const unique_ptr 的传播常量

Kan*_* Li 5 c++ unique-ptr c++11 c++17

以下示例取自cppreference

#include <iostream>
#include <memory>
#include <experimental/propagate_const>

struct X
{
    void g() const { std::cout << "g (const)\n"; }
    void g() { std::cout << "g (non-const)\n"; }
};

struct Y
{
    Y() : m_ptrX(std::make_unique<X>()) { }

    void f() const
    {
        std::cout << "f (const)\n";
        m_ptrX->g();
    }

    void f()
    {
        std::cout << "f (non-const)\n";
        m_ptrX->g();
    }

    std::experimental::propagate_const<std::unique_ptr<X>> m_ptrX;
};

int main()
{
    Y y;
    y.f();

    const Y cy;
    cy.f();
}
Run Code Online (Sandbox Code Playgroud)

我想进一步确保指针 ( m_ptrX) 地址不可修改,因此我将声明更改为

std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;
Run Code Online (Sandbox Code Playgroud)

但是不行,gcc 9报如下错误(详见这里

g++ -std=c++2a -pthread  -O2 -Wall -Wextra -pedantic -pthread -pedantic-errors main.cpp -lm  -latomic -lstdc++fs  && ./a.out

In file included from main.cpp:3:

/usr/local/include/c++/9.2.0/experimental/propagate_const: In instantiation of 'constexpr std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type* std::experimental::fundamentals_v2::propagate_const<_Tp>::get() [with _Tp = const std::unique_ptr<X>; std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type = X]':

/usr/local/include/c++/9.2.0/experimental/propagate_const:205:13:   required from 'constexpr std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type* std::experimental::fundamentals_v2::propagate_const<_Tp>::operator->() [with _Tp = const std::unique_ptr<X>; std::experimental::fundamentals_v2::propagate_const<_Tp>::element_type = X]'

main.cpp:24:15:   required from here

/usr/local/include/c++/9.2.0/experimental/propagate_const:225:25: error: invalid conversion from 'const element_type*' {aka 'const X*'} to 'std::experimental::fundamentals_v2::propagate_const<const std::unique_ptr<X> >::element_type*' {aka 'X*'} [-fpermissive]

  225 |  return __to_raw_pointer(_M_t);

      |         ~~~~~~~~~~~~~~~~^~~~~~

      |                         |

      |                         const element_type* {aka const X*}
Run Code Online (Sandbox Code Playgroud)

那么实现效果的正确方法是什么,假设我不想实现像immutable_unique_ptr.

小智 5

似乎不可能std::unique_ptr通过将其const放入来防止修改std::experimental::propagate_const,但要了解原因,我们必须查看 的源代码std::experimental::propagate_const,它可在传播常量中获得

propagate_const 有一个私有变量,

private:
      _Tp _M_t;
Run Code Online (Sandbox Code Playgroud)

所以当你创建对象时std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;,这里_Tp是模板参数,它会推导出_Tp = const std::unique_ptr<X>

现在在 的m_ptr->g();行报告错误void Y::f(),非常量重载的f,所以让我们来看看 的所有函数调用propagate_const

因此,m_ptr->g()请调用具有以下实现的非 const operator->()ofpropagate_const

constexpr element_type* operator->(){
    return get();
}
Run Code Online (Sandbox Code Playgroud)

然后调用get()非常量重载,因为operator->()它本身是非常量重载,现在get()有以下实现,

constexpr element_type* get(){
    return __to_raw_pointer(_M_t);
}
Run Code Online (Sandbox Code Playgroud)

最后__to_raw_pointer是私有模板函数propagate_const并具有以下重载,

template <typename _Up> static constexpr element_type* __to_raw_pointer(_Up* __u) { return __u; }

template <typename _Up> static constexpr element_type* __to_raw_pointer(_Up& __u) { return __u.get(); }

template <typename _Up>static constexpr const element_type* __to_raw_pointer(const _Up* __u) { return __u; }

template <typename _Up> static constexpr const element_type* __to_raw_pointer(const _Up& __u) { return __u.get(); }
Run Code Online (Sandbox Code Playgroud)

因为私有数据成员的类型_M_tconst std::unique_ptr<X>并且因此__to_raw_pointer(_M_t)将选择最后一个重载,也就是说,

template <typename _Up> 
static constexpr const element_type* __to_raw_pointer(const _Up& __u) { 
     return __u.get(); 
}
Run Code Online (Sandbox Code Playgroud)

所以这是错误的来源,返回类型const element_type*将推导出const X*非 const 重载get()

constexpr element_type* get() { 
return __to_raw_pointer(_M_t); 
}
Run Code Online (Sandbox Code Playgroud)

有返回类型element_type*,将推导出X*.
很明显现在const X*不能转换为X*,这正是报告的错误。

所以std::experimental::propagate_const<const std::unique_ptr<X>> m_ptrX;不会工作。