c++ - 如何编写 const 传播指针类型包装器?
问题描述
为了更好地理解 C++ 类型系统,我努力编写了一个指针包装类,它传播类似于 std::experimental::propagate_const 的常量:
template <typename Pointee> class Ptr {
public:
Ptr() = delete;
explicit Ptr(Pointee *);
Ptr(const Ptr<Pointee> &) = delete;
Ptr(Ptr<Pointee> &&);
Ptr<Pointee> &operator=(const Ptr<Pointee> &) = delete;
Ptr<Pointee> &operator=(Ptr<Pointee> &&);
~Ptr() = default;
const Pointee *operator->() const;
Pointee *operator->();
const Pointee &operator*() const;
Pointee &operator*();
private:
Pointee *mPtr;
};
包装器旨在提供接近原始指针的行为,同时还强制执行一种“深”的 const 正确性并防止无意的别名。
为此,删除了复制构造函数和复制赋值运算符:
- 通过从 Ptr 复制来防止指向对象的无意别名。
- 通过将 const Ptr 复制到非 const Ptr 中来防止对 const 指向的对象进行非 const 访问。
然而,上述设计有两个不幸的后果。
- 一个 const Ptr 不能移动到一个 const 或非 const Ptr 中。当 C++17 的强制 RVO 不适用时,这意味着无法从函数返回 const Ptr 对象。
- 由于 C++17 的强制复制/移动省略,在某些情况下,即使不存在这样的可行构造函数,也可以从 const Ptr 构造非常量 Ptr。例如,下面的代码可以编译得很好(为了演示的目的,忽略内存泄漏/raw new):
const Ptr<int> allocateImmutableInt(int val) { return Ptr<int>(new int(val)); }
void foo() {
Ptr<int> immutableInt = allocateImmutableInt(0); // Initializes non-const Ptr from const Ptr
*immutableInt = 100; // Oops, changed value of 'immutable' object
}
第一个问题可以通过引入一个接受 const 右值引用的 move ctor 来部分解决(尽管这感觉有点奇怪和不习惯):
Ptr(const Ptr<Pointee> &&);
然而,这实际上恶化了第二个问题。现在,即使没有强制移动/复制省略,也可以将 const Ptr 构造为非常量 Ptr。据我所知,要解决这个问题,我们需要一个所谓的“const 构造函数”,即一个只能被调用以生成 const 对象的构造函数:
Ptr(const Ptr<Pointee>&&) const;
即使 c++ 支持这样的构造函数,第二个问题仍然存在,因为 c++17 在决定初始化对象时是否可以应用强制移动/复制省略时特别忽略了 cv 限定和构造函数的可行性。目前似乎没有办法让 c++ 在将强制复制/移动省略应用于对象初始化之前检查复制/移动是否可行。
据我所知, std::experimental::propagate_const 也存在同样的问题。我想知道我是否遇到了 c++ 的基本限制,或者我是否错误地设计了 Ptr 包装器?我知道这些问题很可能可以通过创建两种类型来消除,即用于非常量访问的 Ptr 和用于仅 const 访问的 ConstPtr。但是,这首先破坏了创建 const-propagating 包装器的目的。
也许我刚刚偶然发现了迭代器类型和 const_iterator 类型都存在的原因。
解决方案
Ptr<int> const allocateImmutableInt(int val) {
return Ptr<int>(new int(val));
}
不做你认为它做的事。它不会创建一个 const 限定的对象,然后将其传递给immutableInt
. - 这甚至是不可能的,因为你删除了复制构造函数。- 相反,编译器会推断
Ptr<int> immutableInt = allocateImmutableInt(0);
作为
Ptr<int> immutableInt(new int(val));
如果您将主要功能编写为:
void foo() {
Ptr<int> const immutableInt = allocateImmutableInt(0);
Ptr<int> mutableInt = std::move(immutableInt); // doesn't compile
*mutableInt = 100;
}
您将看到正确的行为:move
由于 const 是不可能的,并且复制构造函数被删除。
编辑:顺便说一句,你的Ptr
课看起来很像std::unique_ptr
. 因此你可以写:
#include <memory>
void foo() {
std::unique_ptr<int> const immutableInt = std::make_unique<int>(0);
std::unique_ptr<int> mutableInt = std::move(immutableInt); // doesn't compile
*mutableInt = 100;
}
如果你想要一个 const 限定的指针,写
std::unique_ptr<int const>
推荐阅读
- typescript - 如何使用 TypeScript Playground 导入库
- python - 如何使用 convert_coreml 转换自定义管道(分类 get_dummies)?
- r - R DT & blogdown - 创建表导致“路径太长”
- python - 为什么推文总是被识别为字节,而不是字符串?
- c++ - 具有 unique_ptr 的结构向量
- python-3.x - 有没有一种计算上更有效的方法来将数千个列表与边界值进行比较?Python
- java - 如何在邮递员中发送包含数组列表的对象
- r - 更新 R 表达式
- javascript - Electron:动态上下文菜单
- c# - 从 Mono.Cecil 获取 .NET 类型