const unique_ptr 的propagate_const

Posted

技术标签:

【中文标题】const unique_ptr 的propagate_const【英文标题】:propagate_const of const unique_ptr 【发布时间】:2020-03-15 04:19:41 【问题描述】:

以下示例取自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();

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

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

但是不行,gcc 9报如下错误(详见here)

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*

那么实现效果的正确方法是什么,假设我不想实现immutable_unique_ptr这样的模板。

【问题讨论】:

这个propagate_const 看起来像另一个样板,你可能想要坚持你班级中的几乎每个成员指针。有它的道理,说得通,但是它让代码越来越长…… 它可以使用 GCC 9.2 C++14 及更高版本在我的系统上轻松编译。可能是系统特定的吗?另请参阅:wandbox.org/permlink/nL9huBh5yJVc11Ks @AniketChowdhury 问题是为什么当std::experimental::propagate_const&lt;std::unique_ptr&lt;X&gt;&gt; m_ptrX;std::experimental::propagate_const&lt;const std::unique_ptr&lt;X&gt;&gt; m_ptrX; 替换时它不起作用。 【参考方案1】:

似乎不可能通过在std::experimental::propagate_const 中将const 设置为const 来保护std::unique_ptr 免受修改,但要了解原因,我们必须通过std::experimental::propagate_const 的源代码,它可以在propagate_const 获得。

propagate_const 有一个私有变量,

private:
      _Tp _M_t;

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

现在在void Y::f()m_ptr-&gt;g(); 行报告错误,f 的非 const 重载,所以让我们检查一下propagate_const 的所有函数调用。

所以m_ptr-&gt;g() 调用propagate_const 的非常量operator-&gt;() 并具有以下实现,

constexpr element_type* operator->()
    return get();

然后它调用get(),非 const 重载,因为 operator-&gt;() 它本身是非 const 重载,现在 get() 有以下实现,

constexpr element_type* get()
    return __to_raw_pointer(_M_t);

最后__to_raw_pointerpropagate_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(); 

因为私有数据成员 _M_t 的类型是 const std::unique_ptr&lt;X&gt; 并且由于这个 __to_raw_pointer(_M_t) 将选择最后一个重载,也就是说,

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

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

constexpr element_type* get()  
return __to_raw_pointer(_M_t); 

返回类型为element_type*,将推导出为X*。 现在很清楚const X* 无法转换为X*,这正是报告的错误。

所以std::experimental::propagate_const&lt;const std::unique_ptr&lt;X&gt;&gt; m_ptrX; 将不起作用。

【讨论】:

但问题是这是否是一个实现错误,或者标准是否真的不允许const std::unique_ptr&lt;X&gt; 作为std::experimental::propagate_const 的参数。我找不到任何会禁止它的要求。【参考方案2】:

tldr 错误

Library Fundamentals TS v2 表示非常量 propagate_const&lt;T&gt;::operator-&gt;element_typeX):

    constexpr element_type* operator-&gt;() 需要:get() != nullptr 返回:get()

它以非常量get的形式指定,因此指定:

    constexpr element_type* get(); 如果T 是对象指针类型,则返回:t_,否则返回t_.get()

即使T 是常量,这也是完全有效的。

我们可以寻找类型本身的需求。 TS 在 [propagate_const.requirements]、[propagate_const.class_type_requirements] 和表 4 中对 propagate_const 的模板参数提出了要求。

所有这些要求都满足或不适用。对T的简历资格没有要求。

我能找到的最有趣的一句话是在语句 [propagate_const.class_type_requirements]/1:

在此子句中,t 表示T 类型的非常量左值,ct 是绑定到tconst T&amp;element_type 表示对象类型。

这句话是LWG issue 3136的主题,但这无助于阐明它,只是暗示它可能不是以典型的风格起草或草率地起草

有人可能会争辩说,这个子句隐含地要求可以有一个T 类型的非常量左值,因此T 不能是 const。这似乎很紧张。该子句对T 类型的非常量左值应用约束,并且不要求存在任何此类约束。但是,它还对绑定到非 const 左值的 const 的引用应用约束,而不是 const T 类型的左值。因此,ct 的要求也不适用,这是荒谬的。所以,我的结论是,这是草率的措辞,这句话并没有隐含禁止 const T。非常量要求不应该适用。

【讨论】:

以上是关于const unique_ptr 的propagate_const的主要内容,如果未能解决你的问题,请参考以下文章

将 std::unique_ptr 类成员标记为 const

将 const std::unique_ptr 用于 pimpl 习惯用法

`unique_ptr< T const [] >` 是不是应该接受 `T*` 构造函数参数?

可克隆的类层次结构和 unique_ptr

矢量的独特副本

将 unique_ptr 与成员函数指针一起使用时出错