因此,当我发现shared_ptr
类(及其weak_ptr
兄弟)时,我很喜欢它。但是后来,我们中的一个人说它太重和太贵了。所以现在我们只剩下原始指针了,还有一个相当繁重的多线程管道。由于存在错误,我们遇到了许多无法进行null保护的崩溃,因为指针实际上并非为null(但是它们是垃圾)。我想到了以下构造:
template<class T>
class GoodPtr {
public:
GoodPtr() {
std::lock_guard<std::mutex> lock(mux);
goodPtrs.emplace((T*)this);
}
virtual ~GoodPtr() {
std::lock_guard<std::mutex> lock(mux);
goodPtrs.erase((T*)this);
}
static bool isGoodPtr(T* ptr) {
// would love to use shared_mutex, but apparently it's not in c++11, so...
std::lock_guard<std::mutex> lock(mux);
return goodPtrs.find((T*)ptr) != goodPtrs.end();
}
private:
static std::unordered_set<T*> goodPtrs;
static std::mutex mux;
};
template<class T> std::unordered_set<T*> GoodPtr<T>::goodPtrs;
template<class T> std::mutex GoodPtr<T>::mux;
template <class T>
class GoodPtrHolder {
public:
GoodPtrHolder(){}
GoodPtrHolder(T* ptr) : _ptr(ptr) {}
void setPtr(T* ptr) {
_ptr = ptr;
}
T* operator->() {
if (_ptr && GoodPtr<T>::isGoodPtr(_ptr)) {
return _ptr;
} else {
static T dummy;
return &dummy;
}
}
private:
T* _ptr = nullptr;
};
给出以下示例:
class testo : public GoodPtr<testo> {
public:
testo() {}
virtual ~testo(){}
void doSomething() {
printf("sup %d\n", x);
}
void setX(int val) { x = val; }
private:
int x;
};
和:
auto *test = new testo;
GoodPtrHolder<testo> testHolder(test);
testHolder->setX(5);
testHolder->doSomething();
delete test;
testHolder->doSomething();
考虑到我必须保留静态的指针表,这比shared_ptr
好还是坏?有解决这个问题的更好方法吗?
答案 0 :(得分:0)
除了其他所有内容之外,您的代码在这里还有未定义的行为:
delete test;
testHolder->doSomething();
// ends up here:
if (_ptr && GoodPtr<T>::isGoodPtr(_ptr)) {
// ^^^^
问题在于_ptr
是test
的副本,删除test
会使指针值无效。
在C ++ 98中,[expr.delete]
说:
- 删除表达式中的 cast-expression 应该被精确评估一次。如果 delete-expression 调用实现释放函数(3.7.3.2),并且delete表达式的操作数不是空指针常量,则释放函数将释放指针< strong>因此使指针无效。 [注意:指向释放内存的指针的值不确定。 ]
或者在this C++11 draft,[basic.stc.dynamic.deallocation]
中:
- 如果在标准库中提供给释放函数的参数是不是空指针值(4.10)的指针,则释放函数应释放该指针所引用的存储,使所有引用的指针无效已分配存储空间 的任何部分。使用无效的指针值(包括将其传递给释放函数)的效果是不确定的。
(全部为我的重点。)
在delete test
之后,仅查看指针值会调用未定义的行为。