C++:自定义删除器状态
C++: custom deleter status
我声明了一个类型
template <typename T>
using SmartPtr = std::unique_ptr<T, MyDeleter>;
在某些特定情况下,我想计算对对象的引用并在指针超出范围时有条件地删除它。
我知道有 std::shared_ptr class 但我有一些对象想通过通用接口访问。其中一些对象由它们的 class 所有,其他 classes 有一个工厂方法,可以产生它们创建的对象的所有权。
我的问题是:
- 是否每个
SmartPtr
类型的指针都有自己的个人 MyDeleter
对象?
- 多个
std::unique_ptr
指向同一个对象是否被认为是一种糟糕的编码习惯?即使我使用自定义删除器?
我觉得在没有删除器的情况下使用 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>;
是一种可能是唯一指针的类型。
用{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
, 那么我可以从任何一个源读取相同的内容。
我声明了一个类型
template <typename T>
using SmartPtr = std::unique_ptr<T, MyDeleter>;
在某些特定情况下,我想计算对对象的引用并在指针超出范围时有条件地删除它。
我知道有 std::shared_ptr class 但我有一些对象想通过通用接口访问。其中一些对象由它们的 class 所有,其他 classes 有一个工厂方法,可以产生它们创建的对象的所有权。
我的问题是:
- 是否每个
SmartPtr
类型的指针都有自己的个人MyDeleter
对象? - 多个
std::unique_ptr
指向同一个对象是否被认为是一种糟糕的编码习惯?即使我使用自定义删除器?
我觉得在没有删除器的情况下使用 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>;
是一种可能是唯一指针的类型。
用{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
, 那么我可以从任何一个源读取相同的内容。