C ++:如何防止对参数中构造的对象进行破坏?

时间:2010-01-15 16:16:01

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

我正在寻找这样的工作人员。

有一个类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类怎么办?

7 个答案:

答案 0 :(得分:5)

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对象就会发生。

答案 1 :(得分:2)

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

我建议Boost Smart Pointers

答案 2 :(得分:2)

撰写以下内容时:

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

传递给A的构造函数的B对象是一个临时对象,一旦A的构造函数完成(如你所观察到的那样)就会被破坏。只要A需要,您就需要B对象存活(因为A使用对象的引用)。

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

答案 3 :(得分:1)

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

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

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

如果您切换到指针,则会向A的析构函数添加删除调用,并且您的实例化将如下所示:

A a(new B(1));

答案 4 :(得分:1)

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

首先,您可以通过微妙的变化保留您的设计:

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

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

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

有许多所谓的智能指针。 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 :(得分:0)

C ++ Standard 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引用临时文件。

答案 6 :(得分:0)

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

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