【问题标题】:Pointer to stack object without ownership指向没有所有权的堆栈对象的指针
【发布时间】:2026-01-01 13:50:01
【问题描述】:

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

示例:

class foo{
public:
    bar* pntr
};

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

【问题讨论】:

    标签: c++ pointers c++11 smart-pointers


    【解决方案1】:

    原始指针在这里非常好。 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 也必须有一个简单的自定义删除器,否则它会破坏指向自动变量的指针的堆栈。

    【讨论】:

    • 你真的应该链接到the most recent revision (N4282),或std::experimental::observer_ptr。此外,reference_wrapper 是一个选项,如果指针不应该为空。
    • @vsoftco:谢谢。起初我确实有一个不同的笑话,但它是垃圾所以我deleted 它。
    • @vsoftco:太好了。感谢您忍受我的weak 笑话。
    • @LightnessRacesinOrbit 甚至weak 的笑话也可以是shared
    • @Voo 所以例如您(或您图书馆的客户)不会错误地删除它。从某种意义上说,您清楚地表明该指针是非拥有的。我链接的论文摘要提到:“因此,它旨在作为原始指针类型的近乎直接的替代品,其优点是,作为一种词汇类型,它表明其预期用途而不需要供代码阅读器详细分析”.
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      如果“更好的方法”是指“更安全的方法”,那么是的,我在这里实现了一个“非拥有”智能指针: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++ 程序员至少应该可以选择避免无效内存访问的不必要风险。原始指针也可以是一个有效的选项,但我认为这取决于上下文。如果它是一个复杂的软件,其中安全性比性能更重要,那么更安全的替代方案可能会更好。

      【讨论】:

        【解决方案4】:

        原始指针的问题在于无法判断它是否仍指向有效对象。幸运的是,std::shared_ptr 有一个aliasing constructor,您可以使用它来有效地将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。 IE。您不能仅对分配在堆栈上的对象执行此操作。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • 这不正确。如果指针指向自动变量,那么最终会破坏堆栈。
          • 好点,我不清楚这部分。我建议在任何情况下都阅读说明,以避免出现类似的常见陷阱。
          • 我同意这部分的概念(Urlich 和其他人):C++ 有太多的内存模型(对于大型应用程序,通用计算),同意这种“过度设计”(某种)可以减少例如使用来自静态/已分配/等智能指针的已分配数据部分。相反:低硬件项目需要使用更激进的代码。 BTW 堆栈在许多 uC 上是高度有限的资源