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

15

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

class foo{
public:
    bar* pntr
};

int main(){
    bar a;
    foo b;
    b.pntr=&a;
}
5个回答

23

在这里使用原始指针是完全可以的。C++11没有任何其他处理非拥有对象的“愚蠢”智能指针,因此您不能使用C++11智能指针。有一个提案用于非拥有对象的“愚蠢”智能指针:

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也可能是一种解决方案,因为后者也是一种非拥有智能指针。然而,std::weak_ptr只能从std::shared_ptr或另一个std::weak_ptr构造。一个严重的缺点是std::shared_ptr是一个“重量级”对象(因为内部引用计数机制)。请注意,即使在这种情况下,std::shared_ptr必须具有平凡的自定义删除器,否则它会破坏指向自动变量的指针的堆栈。


3
你应该真正链接到最新修订版(N4282),或者链接到std :: experimental :: observer_ptr。另外,如果指针永远不应为null,则reference_wrapper也是一种选择。 - T.C.
1
@vsoftco:谢谢。我原本有一个不同的笑话,但它很糟糕,所以我把它删除了。 - Lightness Races in Orbit
1
@LightnessRacesinOrbit 即使是“无力”的笑话也可以被“分享”。 - Hatted Rooster
1
@Voo 所以,例如您(或您库的客户端)不会错误地删除它。在某种意义上,您使其清楚指针是非所有权的。我链接的论文摘要提到:“因此,它旨在成为原始指针类型的近似替代品,具有词汇类型的优势,无需代码读者进行详细分析即可指示其预期使用方式”。 - vsoftco
1
@vsoftco 好的。在我的代码中,我现在认为任何原始指针都是非拥有的,但我可以看到将其编码的优点。 - Voo
显示剩余12条评论

3
在这里使用裸指针是完全可以的,因为您不打算让指针拥有所指资源的所有权。

1
使用裸指针的问题在于无法确定它是否仍然指向有效对象。幸运的是,std::shared_ptr 有一个别名构造函数,你可以使用它来有效地创建一个具有自动存储期的类成员的 std::weak_ptr。例如:
#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。也就是说,您不能完全使用在堆栈上分配的对象来实现此目的。


0
如果你所说的“更好的方法”是指“更安全的方法”,那么是的,我在这里实现了一个“非拥有”的智能指针: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,它比TRegisteredPointer略慢,但几乎可以在任何情况下用作原始指针的替代品。(特别是在目标类型完全定义之前可以使用它,而这不是TRegisteredPointer的情况。)

就您的问题的情感而言,我认为它是有效的。到现在为止,C++程序员应该至少有避免无效内存访问风险的选择。原始指针也可能是一个有效的选择,但我认为这取决于上下文。如果它是一种复杂的软件,在其中安全性比性能更重要,则更安全的替代方案可能更好。


-2

只需动态分配对象并使用shared_ptr即可。是的,它实际上会删除该对象,但仅当它是具有引用的最后一个对象时才会删除。此外,它还可以防止其他人删除它。这正是正确的做法,既避免了内存泄漏,也避免了悬空指针。还要查看相关的weap_ptr,如果指向物的生命周期要求不同,您也可以利用它。


2
那不正确。如果指针指向自动变量,那么最终会破坏堆栈。 - vsoftco
好的,我没有清楚表达这一部分。无论如何,我建议阅读说明书,以避免像这样的常见陷阱。 - Ulrich Eckhardt
1
在这方面,我同意Urlich和其他人的观点:C++有太多的内存模型(适用于大型应用程序、通用计算),同意这种“过度设计”(某种程度上)可以通过使用来自静态/分配/等智能指针的分配数据部分来减少。相反,低硬件项目需要使用更具攻击性的代码。顺便说一句,在许多uC上,堆栈是高限制资源。 - Jacek Cz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接