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<std::unique_ptr<X>> m_ptrX;
被std::experimental::propagate_const<const std::unique_ptr<X>> 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<const std::unique_ptr<X>> m_ptrX;
时,这里_Tp
是模板参数,它会推导出为_Tp = const std::unique_ptr<X>
现在在void Y::f()
的m_ptr->g();
行报告错误,f
的非 const 重载,所以让我们检查一下propagate_const
的所有函数调用。
所以m_ptr->g()
调用propagate_const
的非常量operator->()
并具有以下实现,
constexpr element_type* operator->()
return get();
然后它调用get()
,非 const 重载,因为 operator->()
它本身是非 const 重载,现在 get()
有以下实现,
constexpr element_type* get()
return __to_raw_pointer(_M_t);
最后__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();
因为私有数据成员 _M_t
的类型是 const 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();
所以这是错误的来源,返回类型是 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<const std::unique_ptr<X>> m_ptrX;
将不起作用。
【讨论】:
但问题是这是否是一个实现错误,或者标准是否真的不允许const std::unique_ptr<X>
作为std::experimental::propagate_const
的参数。我找不到任何会禁止它的要求。【参考方案2】:
tldr 错误
Library Fundamentals TS v2 表示非常量 propagate_const<T>::operator->
(element_type
是 X
):
constexpr element_type* operator->()
需要: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
是绑定到t
的const T&
,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 习惯用法