使用 raw_ptr 表示 unique_ptr 是指向 类 的 nullptr
signaling that unique_ptr is nullptr to classes using raw_ptr
我有底层代码。有没有办法向原始指针发出信号,表明它对应的 unique_ptr
已被重置?
std::unique_ptr<int> pi = std::make_unique<int>(3);
int* rpi = pi.get();
std::cout << "rpi = " << *rpi << std::endl;
pi.reset();
if( rpi != nullptr) // this is always the case
{
std::cout << "rpi = " << *rpi << std::endl;
}
else
{
std::cout << "nullptr" << std::endl;
}
假设您的原始指针值可能在程序的任何地方结束,不,您不能。想象一下,您需要某种结构,其中包含指向为您的智能指针创建的唯一全局变量的引用或指针。该变量存储状态,而不是您传递的“结构指针”。它会起作用。
那么这个结构在 C++ 中叫什么?它是 weak_ptr
,它的状态仅来自 shared_ptr
。为什么?因为对于该指针的任何使用,您必须安全地声明其所有权,这意味着共享所有权。
简而言之:不,你不能。
但更重要的是:您不能在重置后使用原始指针!
当您调用pi.reset()
时,将调用底层删除器。在您的示例中,此调用将等效于 delete rpi
。众所周知,这样删除内存地址后,就不能再解引用了。
换句话说:这一行
std::cout << "rpi = " << *rpi << std::endl;
调用未定义的行为。如果它确实像您期望的那样打印出数字 3,那纯属偶然。您的程序可能会在此行崩溃或执行其他任何操作...
好吧,你可以通过一些假设来解决这个任务,使用自定义删除仿函数。代码见下,代码后有解释:
#include <memory>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <iostream>
template <typename T>
class RefDeleter {
public:
RefDeleter() {
std::unique_lock lock(mux_);
id_ = ++gid_;
}
~RefDeleter() {
std::unique_lock lock(mux_);
refs_.erase(id_);
}
RefDeleter & operator ()(T * ptr) {
{
std::unique_lock lock(mux_);
auto it = refs_.find(id_);
if (it != refs_.end())
for (auto e: it->second)
*e = nullptr;
}
delete ptr;
return *this;
}
RefDeleter & AddRef(T ** ptr) {
std::unique_lock lock(mux_);
refs_[id_].insert(ptr);
return *this;
}
RefDeleter & DelRef(T ** ptr) {
std::unique_lock lock(mux_);
refs_[id_].erase(ptr);
return *this;
}
private:
uint64_t id_ = 0;
static inline std::mutex mux_;
static inline uint64_t gid_ = 0;
static inline std::unordered_map<uint64_t,
std::unordered_set<T **>> refs_;
};
static auto l = [](int* p) { delete p; };
template <typename T>
using ref_unique_ptr = std::unique_ptr<T, RefDeleter<T>>;
template <typename T>
class RefDeleterScope {
public:
RefDeleterScope(ref_unique_ptr<T> & rup, T * & raw)
: rup_(rup), raw_(raw) {
rup_.get_deleter().AddRef(&raw_);
}
~RefDeleterScope() {
rup_.get_deleter().DelRef(&raw_);
}
private:
ref_unique_ptr<T> & rup_;
T * & raw_;
};
#define GETRUP(raw, unique) \
raw = unique.get(); RefDeleterScope ref_del_scope_##__LINE__(unique, raw);
int main() {
{
ref_unique_ptr<int> up;
up.reset(new int(123));
int * rp; GETRUP(rp, up);
std::cout << rp << " " << (rp ? *rp : 0) << std::endl;
up.reset();
std::cout << rp << " " << (rp ? *rp : 0) << std::endl;
}
}
输出:
0x1182eb0 123
0 0
如您所见,使用下一个假设可以解决任务:
现在你应该到处使用 std::unique_ptr<int, RefDeleter<int>>
而不仅仅是 std::unique_ptr<int>
。我将第一个类型命名为 ref_uniqe_ptr<int>
为了更短的方便。
您不能使用 int * rp = up.get();
,只能使用 int * rp; GETRUP(rp, up);
,并且您应该在任何地方都这样使用,第一种用法会破坏解决方案。 GETRUP(rp, up)
将唯一指针 up
的指针分配给原始指针 rp
。如您所见,GETRUP()
宏分配了一个单独的清理器 class 对象,该对象使用 RAII 在作用域结束后进行清理。
这个原始指针rp
现在有一个块生命周期,这意味着你不能在它的范围之外复制rp
,这意味着你不能做int * rp2 = rp;
,你应该只做 int * rp2; GETRUP(rp2, up);
。这意味着只要你需要原始指针的副本,就必须使用 GETRUP()
。因为每个新的原始指针都被分配了一个单独的引用,这是可更新的。
重置后所有原始指针都将变为 nullptr。即使您将唯一指针重置为某个非空值,所有引用原始指针的内容仍然会变为 nullptr。只有在重置后创建的新原始指针才会保存非空值。所以我的解决方案只在重置时清除指向 null 的原始指针,它不会更新为新的非空值。如果你愿意,你总是可以通过使用 GETRUP()
.
重新分配原始指针的值(如果它变为空)
我有底层代码。有没有办法向原始指针发出信号,表明它对应的 unique_ptr
已被重置?
std::unique_ptr<int> pi = std::make_unique<int>(3);
int* rpi = pi.get();
std::cout << "rpi = " << *rpi << std::endl;
pi.reset();
if( rpi != nullptr) // this is always the case
{
std::cout << "rpi = " << *rpi << std::endl;
}
else
{
std::cout << "nullptr" << std::endl;
}
假设您的原始指针值可能在程序的任何地方结束,不,您不能。想象一下,您需要某种结构,其中包含指向为您的智能指针创建的唯一全局变量的引用或指针。该变量存储状态,而不是您传递的“结构指针”。它会起作用。
那么这个结构在 C++ 中叫什么?它是 weak_ptr
,它的状态仅来自 shared_ptr
。为什么?因为对于该指针的任何使用,您必须安全地声明其所有权,这意味着共享所有权。
简而言之:不,你不能。
但更重要的是:您不能在重置后使用原始指针!
当您调用pi.reset()
时,将调用底层删除器。在您的示例中,此调用将等效于 delete rpi
。众所周知,这样删除内存地址后,就不能再解引用了。
换句话说:这一行
std::cout << "rpi = " << *rpi << std::endl;
调用未定义的行为。如果它确实像您期望的那样打印出数字 3,那纯属偶然。您的程序可能会在此行崩溃或执行其他任何操作...
好吧,你可以通过一些假设来解决这个任务,使用自定义删除仿函数。代码见下,代码后有解释:
#include <memory>
#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#include <iostream>
template <typename T>
class RefDeleter {
public:
RefDeleter() {
std::unique_lock lock(mux_);
id_ = ++gid_;
}
~RefDeleter() {
std::unique_lock lock(mux_);
refs_.erase(id_);
}
RefDeleter & operator ()(T * ptr) {
{
std::unique_lock lock(mux_);
auto it = refs_.find(id_);
if (it != refs_.end())
for (auto e: it->second)
*e = nullptr;
}
delete ptr;
return *this;
}
RefDeleter & AddRef(T ** ptr) {
std::unique_lock lock(mux_);
refs_[id_].insert(ptr);
return *this;
}
RefDeleter & DelRef(T ** ptr) {
std::unique_lock lock(mux_);
refs_[id_].erase(ptr);
return *this;
}
private:
uint64_t id_ = 0;
static inline std::mutex mux_;
static inline uint64_t gid_ = 0;
static inline std::unordered_map<uint64_t,
std::unordered_set<T **>> refs_;
};
static auto l = [](int* p) { delete p; };
template <typename T>
using ref_unique_ptr = std::unique_ptr<T, RefDeleter<T>>;
template <typename T>
class RefDeleterScope {
public:
RefDeleterScope(ref_unique_ptr<T> & rup, T * & raw)
: rup_(rup), raw_(raw) {
rup_.get_deleter().AddRef(&raw_);
}
~RefDeleterScope() {
rup_.get_deleter().DelRef(&raw_);
}
private:
ref_unique_ptr<T> & rup_;
T * & raw_;
};
#define GETRUP(raw, unique) \
raw = unique.get(); RefDeleterScope ref_del_scope_##__LINE__(unique, raw);
int main() {
{
ref_unique_ptr<int> up;
up.reset(new int(123));
int * rp; GETRUP(rp, up);
std::cout << rp << " " << (rp ? *rp : 0) << std::endl;
up.reset();
std::cout << rp << " " << (rp ? *rp : 0) << std::endl;
}
}
输出:
0x1182eb0 123
0 0
如您所见,使用下一个假设可以解决任务:
现在你应该到处使用
std::unique_ptr<int, RefDeleter<int>>
而不仅仅是std::unique_ptr<int>
。我将第一个类型命名为ref_uniqe_ptr<int>
为了更短的方便。您不能使用
int * rp = up.get();
,只能使用int * rp; GETRUP(rp, up);
,并且您应该在任何地方都这样使用,第一种用法会破坏解决方案。GETRUP(rp, up)
将唯一指针up
的指针分配给原始指针rp
。如您所见,GETRUP()
宏分配了一个单独的清理器 class 对象,该对象使用 RAII 在作用域结束后进行清理。这个原始指针
rp
现在有一个块生命周期,这意味着你不能在它的范围之外复制rp
,这意味着你不能做int * rp2 = rp;
,你应该只做int * rp2; GETRUP(rp2, up);
。这意味着只要你需要原始指针的副本,就必须使用GETRUP()
。因为每个新的原始指针都被分配了一个单独的引用,这是可更新的。重置后所有原始指针都将变为 nullptr。即使您将唯一指针重置为某个非空值,所有引用原始指针的内容仍然会变为 nullptr。只有在重置后创建的新原始指针才会保存非空值。所以我的解决方案只在重置时清除指向 null 的原始指针,它不会更新为新的非空值。如果你愿意,你总是可以通过使用
重新分配原始指针的值(如果它变为空)GETRUP()
.