指向没有所有权的堆栈对象的指针

Pointer to stack object without ownership

本文关键字:堆栈 指针 对象 所有权      更新时间:2023-10-16

我想要一个带有指针成员变量的类。这个指针应该指向一个对象,该对象可能是堆栈分配的,也可能是堆分配的。但是,该指针不应具有任何所有权。换句话说,当指针超出范围时,根本不应该调用delete。我认为一个原始指针可以解决这个问题。。。然而,我不确定是否有比原始指针更好的C++11方法?

示例:

class foo{
public:
    bar* pntr
};
int main(){
    bar a;
    foo b;
    b.pntr=&a;
}

这里的原始指针非常好。C++11没有任何其他处理非拥有对象的"愚蠢"智能指针,因此您不能使用C++11智能指针。有一个针对非自有对象的"愚蠢"智能指针的建议:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4282.pdf

已经实验性地实现为CCD_ 1(感谢@T.C.的提示)。

另一种选择是使用智能指针和一个什么都不做的自定义删除程序:

#include <memory>
int main()
{
    int a{42};
    auto no_op = [](int*){};
    std::unique_ptr<int, decltype(no_op)> up(&a, no_op);
}

或者,正如@T.C.在评论中提到的,std::reference_wrapper

正如@Lightness Races in Orbit所提到的,std::weak_ptr也可能是一个解决方案,因为后者也是一个非拥有的智能指针。然而,std::weak_ptr只能由std::shared_ptr或另一个std::weak_ptr构成。一个严重的缺点是std::shared_ptr是一个"重"对象(因为内部引用计数机制)。请注意,即使在这种情况下,std::shared_ptr也必须有一个微不足道的自定义删除程序,否则它会破坏指向自动变量的指针的堆栈。

在这里使用原始指针是完全可以的,因为您不想让指针拥有所指向资源的所有权。

原始指针的问题在于无法判断它是否仍然指向有效对象。幸运的是,std::shared_ptr有一个别名构造函数,您可以使用它将std::experimental::observer_ptr0有效地生成为具有自动存储持续时间的类成员。示例:

#include <iostream>
#include <memory>
using namespace std;
struct A {
    int x;
};
void PrintValue(weak_ptr<int> wp) {
    if (auto sp = wp.lock())
        cout << *sp << endl;
    else
        cout << "Object is expired." << endl;
}
int main() {
    shared_ptr<A> a(new A);
    a->x = 42;
    weak_ptr<int> wpInt (shared_ptr<int>(a, &a->x));
    PrintValue(wpInt);
    a.reset();  //a->x has been destroyed, wpInt no longer points to a valid int
    PrintValue(wpInt);
    return 0;
}

打印:

42

对象已过期。

这种方法的主要好处是weak_ptr不会阻止对象超出范围并被删除,但同时它可以安全地检测对象何时不再有效。缺点是增加了智能指针的开销,而且最终需要一个对象的shared_ptr。也就是说,不能只对堆栈上分配的对象执行此操作。

如果"更好的方法"是指"更安全的方法",那么是的,我在这里实现了一个"非拥有"智能指针:https://github.com/duneroadrunner/SaferCPlusPlus.(无耻的插头警报,但我认为它在这里是相关的。)所以你的代码看起来像这样:

#include "mseregistered.h"
...
class foo{
public:
    mse::TRegisteredPointer<bar> pntr;
};
int main(){
    mse::TRegisteredObj<bar> a;
    foo b;
    b.pntr=&a;
}

TRegisteredPointer比原始指针"更聪明",因为它知道目标何时被破坏。例如:

int main(){
    foo b;
    bar c;
    {
        mse::TRegisteredObj<bar> a;
        b.pntr = &a;
        c = *(b.pntr);
    }
    try {
        c = *(b.pntr);
    } catch(...) {
        // b.pntr "knows" that the object it was pointing to has been deleted so it throws an exception. 
    };
}

TRegisteredPointer通常具有比std::shared_ptr更低的性能成本。当您有机会在堆栈上分配目标对象时,成本会低得多。尽管它仍然是相当新的,而且还没有很好的文档记录,但该库包含了它的使用示例(在文件"msetl_example.cpp"的下半部分)。

该库还提供了TRegisteredPointerForLegacy,它比TRegisterdPointer慢一些,但几乎在任何情况下都可以用作原始指针的替代品。(特别是,它可以在目标类型完全定义之前使用,TRegisteredPointer的情况并非如此。)

就你的问题的观点而言,我认为这是正确的。到目前为止,C++程序员至少应该可以选择避免无效内存访问的不必要风险。原始指针也是一个有效的选择,但我认为这取决于上下文。如果这是一个复杂的软件,安全性比性能更重要,那么一个更安全的替代方案可能会更好。

只需动态分配对象并使用shared_ptr。是的,它实际上会删除这个东西,但前提是它是最后一个有引用的东西。此外,它还防止其他人删除它。这正是正确的做法,既可以避免内存泄漏,也可以避免悬挂指针。还可以查看相关的weap_ptr,如果指针对象的生存期要求不同,您也可以使用它。