C++:自定义删除器状态

C++: custom deleter status

我声明了一个类型

template <typename T>
using SmartPtr = std::unique_ptr<T, MyDeleter>;

在某些特定情况下,我想计算对对象的引用并在指针超出范围时有条件地删除它。

我知道有 std::shared_ptr class 但我有一些对象想通过通用接口访问。其中一些对象由它们的 class 所有,其他 classes 有一个工厂方法,可以产生它们创建的对象的所有权。

我的问题是:

我觉得在没有删除器的情况下使用 shared_ptr 是不对的,因为 class 拥有的对象无论如何都无法删除,因为它们不是通过 new 创建的。它们是数组的一部分,class 的成员。并非所有 SmartPtr 指向的对象都是使用 new 运算符创建的。

这是一个有效的 "maybe deletes, maybe not" class:

enum class should_delete {
  yes,
  no,
};
struct maybe_delete {
  should_delete choice = should_delete::yes;
  template<class T>
  void operator()(T* t){
    if (choice!=should_delete::no)
      delete t;
  }
  maybe_delete() = default;
  maybe_delete(should_delete c):choice(c) {}
  maybe_delete(maybe_delete const&)=default;
  // move in terms of copy, then clear state to default:
  maybe_delete(maybe_delete&& o):maybe_delete(o){
    o.choice = should_delete::yes;
  }
};
template<class T>
using maybe_unique_ptr = std::unique_ptr<T, maybe_delete>;

是一种可能是唯一指针的类型。

live exmaple

{ptr, should_delete::no}构造,使指针不删除有问题的对象。 (已移动)此类 maybe_unique_ptr 的副本将具有正确的状态。

请注意,.reset(ptr) 可能很危险,因为该对象获得了之前存在的删除状态。我确保从 maybe_unique_ptr 移出的 maybe_unique_ptr 具有与细心构造的 maybe_delete 移动 ctor.

相同的状态

话虽如此,如果您想编写一个侵入式智能指针,我不会将其作为原始 unique_ptr

如果您查看 std::shared_ptr,它们具有 "god mode" 构造函数:

shared_ptr<T>::shared_ptr( T*, shared_ptr<U> )

这让你有一个指向 T 的共享指针,它将所有引用计数转发到一个完全不同的共享指针。这是为 "member" shared-ptr(以及其他用途)设计的——如果你有一个结构 X 和一个成员 Y,你可以有一个 shared-ptr-to Y 实际引用计数封闭的 X 结构。

对于侵入式智能指针,我会编写一个智能指针。它将存储一个 T* 和一个 "scope guard" 类型的对象以进行减量的清理(作用域守卫甚至可以是一个 unique_ptr 到具有递减的破坏者的引用计数接口)。为智能指针做样板(几十行)。在复制时,克隆作用域守卫和 T*。移动时,移动指针和范围守卫(并清除源)。

构造函数将原始 T* 包装为空 ref_count(如果需要),或 T*ref_count 接口,或两者兼而有之-一.

如果包含的对象有引用计数,我很想使用它,只是为了及早检测关闭问题。


bonus version with arbitrary deleter guarded by maybe_delete。这将允许您有条件地引用减量,例如,而不会过多地混合这两个操作。使用空基 class 优化来防止浪费 space(因为 std::default_delete 是空的)。

您想拥有一个指针成员,它可能拥有也可能不拥有它所指向的事物?简单,关注点分离:

std::unique_ptr<T> maybe_owning;
T* always_pointing;

constructor(const T& copy_and_own)
    :maybe_owning( new T(copy_and_own))
    ,always_pointing(maybe_owning.get())
{}
constructor(T* just_reference)
    :maybe_owning()
    ,always_pointing(just_reference)
{}
void do_task() {
   always_pointing->thing();
}

您有一个始终指向数据的指针,丝毫不关心所有权。
你还有一个智能指针,可以有条件地用来拥有。
所有问题都解决了,没有疯狂的代码。

我实际上经常使用这个,一个 fstream 可能会打开一个文件,也可能不会打开一个文件,而 istream& 有时会连接到 fstream 有时会连接到 cin, 那么我可以从任何一个源读取相同的内容。