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

Pointer to stack object without ownership

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

示例:

class foo{
public:
    bar* pntr
};

int main(){
    bar a;
    foo b;
    b.pntr=&a;
}

在这里使用原始指针完全没问题,因为您不打算让指针拥有所指向资源的所有权。

原始指针在这里非常好。 C++11 没有任何其他处理 non-owning 对象的 "dumb" 智能指针,因此您不能使用 C++11 智能指针。有人提议为 non-owned 个对象使用 "stupid" 智能指针:

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

已经通过实验实现为 std::experimental::observer_ptr(感谢@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 may also be a solution, as the latter is also a non-owning smart pointer. However a std::weak_ptr can only be constructed from a std::shared_ptr 或另一个 std::weak_ptr。一个严重的缺点是 std::shared_ptr 是一个 "heavy" 对象(因为内部引用计数机制)。请注意,即使在这种情况下,std::shared_ptr 也必须有一个简单的自定义删除器,否则它会破坏指向自动变量的指针的堆栈。

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

如果 "better approach" 你的意思是 "safer approach",那么是的,我在这里实现了一个 "non-owning" 智能指针: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 比原始指针 "smarter" 因为它知道目标何时被销毁。例如:

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,它比 TRegisteredPointer 稍慢,但几乎可以在任何情况下用作 drop-in 原始指针的替代品。 (特别是它可以在目标类型完全定义之前使用,而 TRegisteredPointer 则不是这种情况。)

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

原始指针的问题是无法判断它是否仍指向有效对象。幸运的是,std::shared_ptr 有一个 aliasing constructor,您可以使用它有效地将 std::weak_ptr 转换为具有自动存储期限的 class 成员。示例:

#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

Object is expired.

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