【问题标题】:C++: how to prevent destructing of objects constructed in argument?C ++:如何防止破坏在参数中构造的对象?
【发布时间】:2010-01-15 16:16:01
【问题描述】:

我有一个关于这样的工作人员的任务。

有一个 class A 有一个类型为 class B 的对象作为它的成员。由于我希望 B 成为其他类组的基类,因此我需要使用指针或对对象的引用,而不是副本,才能在 A 中正确使用 B 的虚拟方法。但是当我写这样的代码时

class B
  {public:
     B(int _i = 1): i(_i) {};
     ~B() 
       {i = 0; // just to indicate existence of problem: here maybe something more dangerous, like delete [] operator, as well! 
        cout << "B destructed!\n";
       };
     virtual int GetI () const  {return i;}; // for example
   protected:
     int i;
  };

class A
  {public:
      A(const B& _b): b(_b) {}
      void ShowI () {cout <<b.GetI()<<'\n';};
   private:
      const B& b;
  }; 

并以这种方式使用它

B b(1);
A a(b);
a.ShowI();

完美运行:

1
B destructed!

但是

A a(B(1));
a.ShowI();

给出非常不想要的结果:对象 b 创建并且 a.b 被设置为对它的引用,但是在 A 的构造函数完成之后,对象 b 破坏了!输出是:

B destructed!
0

我再重复一遍,在 A 中使用副本而不是对 b 的引用

class A
  {public:
      A(B _b): b(_b) {}
      void ShowI () {cout <<b.GetI()<<'\n';};
   private:
      B b;
  }; 

如果 B 是基类并且 A 将其称为虚函数,则将不起作用。也许我太愚蠢了,因为我不知道编写必要代码以使其完美运行的正确方法(那么我很抱歉!)或者可能根本不那么容易:-(

当然,如果 B(1) 被发送到函数或方法,而不是类构造函数,它工作得很好。当然,我可以使用与this 问题中描述的here 相同的代码来创建B 作为正确可克隆的基对象或派生对象,但是对于这样简单的问题来说似乎太难了?如果我想使用我无法编辑的 B 类怎么办?

【问题讨论】:

    标签: c++ inheritance pointers pass-by-reference


    【解决方案1】:

    a 的初始化完成后,临时对象就会被销毁。并且引用成员将悬空,指的是不再存在的对象。

    解决方案一点也不难也不复杂。多态对象通常不是临时的,而是存在更长的时间。因此,您只需使用智能指针来存储对传递对象的引用并使用new 预先创建对象。请注意,为此,B 类必须有一个虚拟析构函数。 shared_ptr 将关心在您的父对象生命周期结束时销毁该对象。复制时,shared_ptr 将与副本共享指向的对象,并且都指向同一个对象。这对于OnwsPolymorphics 的用户来说并不明显,并且不适用于许多用例,因此您可能希望将其复制构造函数和赋值运算符设为私有:

    struct OwnsPolymorphics {
      explicit OwnsPolymorphics(B *b):p(b) { }
    
    private:
      // OwnsPolymorphics is not copyable. 
      OwnsPolymorphics(OwnsPolymorphics const&);
      OwnsPolymorphics &operator=(OwnsPolymorphics);
    
    private:
      boost::shared_ptr<B> p;
    };
    
    OwnsPolymorphics owns(new DerivedFromB);
    

    或者您使用引用成员并将对象存储在堆栈中并将其传递给构造函数,就像您展示的那样。


    只有在 直接 分配给 const 引用时才能延长临时对象的生命周期:

    B const& b = DerivedFromB(); 
    

    然后您也可以使用非虚拟析构函数并可以在b 上调用虚拟函数 - 只要它们是 const 成员函数。但是,这种特殊处理不是针对类成员进行的,并且它具有需要工作副本构造函数的缺点(多态对象通常不可复制或设计为不可复制 - 它们不是按值标识,而是按标识标识)。任何其他性质的临时性内容在其出现的完整表达式结束后都会被销毁 - 对于您的临时 B 对象也是如此。

    【讨论】:

    • 我以前从未听说过提升共享指针...看不到我是否应该在某处调用删除共享指针 p 的对象,如果我使用 'DerivedFromB b; 这个模型是否仍然有效拥有多态拥有(b);'连同 'OwnsPolymorphics owns(new DerivedFromB);'因为如果我使用通常的指针 'B* p;' 就不会并在析构函数中“删除 p”。
    • 您无需致电delete。如果包含对象的析构函数调用了智能指针的析构函数,智能指针将为您完成这项工作。它不适用于DerivedFromB b; OwnsPolymorphics owns(b); - 该代码似乎没有多大意义。只需执行 OwnsPolymorphics owns(new DerivedFromB); 即可。有 种方法可以让它工作(例如使用带有shared_ptr 的自定义删除器),但它们都会使界面更难看。您应该清楚界面 - 其他构造方式的目的是什么?
    • 对不起,我的例子的想法是使问题中提到的两个结构都“A a(B(1));”和“B b(1);A a(b);”好好工作。就像我可以写的那样,例如,“double b=2.0; double c = b*b; cout b);”没有错误。非常感谢您的建议,但如果不可能让两者都工作,我希望我的变体没有智能指针。
    • 双打是另一回事。你可以复制一个double,它保持双倍。但是如果你复制一个B,你就会把它切片。
    • 您不需要 Boost 或 shared_ptr,因为该对象并未真正共享。你应该使用auto_ptr
    【解决方案2】:

    使用某种智能指针,而不是引用。

    我建议Boost Smart Pointers

    【讨论】:

      【解决方案3】:

      当你写下以下内容时:

      A a(B(1));
      a.ShowI();
      

      传递给 A 的构造函数的 B 对象是一个临时对象,它在 A 的构造函数完成后立即被破坏(如您所见)。您要求 B 对象的生存时间与 A 所需的时间一样长(因为 A 使用对该对象的引用)。

      您可以使用 std::auto_ptr (如果您希望转移所有权语义,即 A 对象拥有传递给其构造函数的 B 对象的所有权)或其他共享指针(例如 Boost shared_ptr)。

      【讨论】:

        【解决方案4】:

        这是一个标准问题,不要害怕。

        首先,您可以通过细微的更改来保留您的设计:

        class A
        {
        public:
          A(B& b): m_b(b) {}
        private:
          B& m_b;
        };
        

        通过使用引用而不是 const 引用,编译器将拒绝对使用临时的 A 的构造函数的调用,因为从临时获取引用是非法的。

        没有(直接)解决方案来实际保留const,因为不幸的是,编译器接受了奇怪的构造&amp;B(),即使这意味着获取一个临时地址(而且他们甚至不害羞地让它成为指向非常量的指针...)。

        有许多所谓的智能指针。基本的,在 STL 中称为std::auto_ptr。另一个(众所周知的)是boost::shared_ptr

        这些指针被认为是智能的,因为它们让您不必(过多地)担心对象的破坏,并且实际上保证它会被破坏,而且是正确的。因此,您永远不必担心对delete 的调用。

        但有一个警告:不要使用std::auto_ptr。它是一只卑鄙的野兽,因为它在复制方面有不自然的行为。

        std::auto_ptr<A> a(new A());  // Building
        a->myMethod();                // Fine    
        
        std::auto_ptr<A> b = a;       // Constructing b from a
        b->myMethod();                // Fine
        a->myMethod();                // ERROR (and usually crash)
        

        问题是复制(使用复制构造)或分配(使用赋值运算符)意味着所有权从复制到复制的转移......非常令人惊讶。

        如果您有权访问即将发布的标准,则可以使用 std::unique_ptr,这与自动指针非常相似,但有以下不良行为:无法复制或分配。

        与此同时,您可以简单地使用boost::shared_ptrstd::tr1::shared_ptr。它们有些相同。

        它们是“引用计数”指针的一个很好的例子。他们很聪明。

        std::vector< boost::shared_ptr<A> > method()
        {
          boost::shared_ptr<A> a(new A());        // Create an `A` instance and a pointer to it
          std::vector< boost::shared_ptr<A> > v;
          v.push_back(a);                         // 2 references to the A instance
          v.push_back(a);                         // 3 references to the A instance
          return v;
        }                                         // a is destroyed, only 2 references now
        
        void function()
        {
          std::vector< boost::shared_ptr<A> > w = method(); // 2 instances
          w.erase(w.begin());                               // remove w[0], 1 instance
        }                                                   // w is destroyed, 0 instance
                                                            // upon dying, destroys A instance
        

        这就是引用计数的含义:副本及其原始指向同一个实例,并且它们共享其所有权。而且只要还有一个还活着,A的实例就存在,被最后一个死去破坏,所以你不用担心!!

        您应该记住,他们确实共享指针。如果您使用一个 shared_ptr 修改对象,则它的所有亲属实际上都会看到更改。您可以使用指针在通常模式下进行复制:

        boost::shared_ptr<A> a(new A());
        boost::shared_ptr<A> b(new A(*a)); // copies *a into *b, b has its own instance
        

        总结一下:

        • 不要使用auto_ptr,你会遇到不好的惊喜
        • 如果可用,请使用unique_ptr,这是您更安全、最容易处理的选择
        • 否则使用share_ptr,但要注意浅拷贝语义

        祝你好运!

        【讨论】:

          【解决方案5】:

          所以您的问题是您正在使用对通过自动分配在堆栈上分配的对象的引用。这意味着当该对象离开范围时,它会自动释放。在代码的情况下,您使用对象的范围与您构造对象的代码相同。

          在类中使用引用的危险在于,如果自动分配,则必须确保引用的对象具有比引用更大的范围,或者如果是动态分配的,则必须确保它不会被释放。

          对于您正在尝试做的事情,将指针存储在类中并让类负责释放可能更有意义。我不会使用智能指针或静态引用,因为这确实是一个简单的问题,可以通过指针正确分配和释放来解决。

          如果您切换到指针,您将添加一个对 A 的析构函数的删除调用,您的实例化将如下所示:

          A a(new B(1));
          

          【讨论】:

          • 好吧,据我所知,它可以在调用 'A a(new B())' 的情况下工作,但如果 'B b();一个(b);'之所以调用 b 的析构函数,是因为在这种情况下会调用两次:一次来自 a,第二次来自存在范围 'B b();'
          【解决方案6】:

          这是可以理解和正确的行为。 我可以建议将分配从堆栈更改为堆。智能指针可以帮助您避免显式调用析构函数。

          std::auto_ptr<B> b(new B())
          A a(*b); //using overriden operator * to get B& 
          

          【讨论】:

            【解决方案7】:

            C++ 标准 8.5.3 第 5 段

            用于制作副本的构造函数应该是可调用的 副本是否实际 完毕。 [示例:

            struct A { };
            struct B : public A { } b;
            extern B f();
            const A& rca = f();  // Either bound to the A sub-object of the B rvalue,
                                 // or the entire B object is copied and the reference
                                 //  is bound to the A sub-object of the copy
            —end example]
            

            问题是A a(B(1)); 非常糟糕。 struct A 获取临时参考。

            【讨论】:

              猜你喜欢
              • 2014-08-12
              • 2011-05-20
              • 1970-01-01
              • 2011-12-06
              • 2020-03-05
              • 2013-09-27
              • 2012-12-29
              • 1970-01-01
              • 2015-05-12
              相关资源
              最近更新 更多