为什么这个析构函数会在创建后立即被调用?

时间:2012-01-15 18:12:28

标签: c++ class constructor destructor

我有以下课程:

class FixedByteStream {
public:
FixedByteStream() : size(0), address(NULL), existing(false) {}
FixedByteStream(int length) : existing(false) {
    size = length;
    address = new char[length];
}
FixedByteStream(int length, char* addr) : existing(true) {
    size = length;
    address = addr;
}
FixedByteStream(string* str, bool includeNull = false) : existing(true) {
    size = (*str).length();
    address = const_cast<char*>((*str).c_str());
    if (includeNull){
        ++size;
    }
}
~FixedByteStream() {
    if (existing == false) {
        delete [] address;
    }
    address = NULL;
}
int getLength() {
    return size;
}
char* getAddressAt(int index) {
    return &(address[index]);
}


char& operator[] (const int index) {
    return address[index];
}
operator char*() {
    return address;
}

private:
    bool existing;
    int size;
    char* address;
};

这是一个能够产生问题的非常简单的测试:

FixedByteStream received;
received = FixedByteStream(12);
received[0] = 't';

Valgrind警告无效写入,调试显示原因。 FixedByteStream received;调用不带参数的构造函数(这有点愚蠢,因为它不能执行任何事情)。 received = FixedByteStream(12);使用整数参数调用构造函数...然后立即调用自身的析构函数,使对象无效。它仍然可以用于某种原因,但我宁愿它不会被置于如此奇怪的困境中,引发警告。

那么,为什么要在那里召唤?我可以稍微了解析构函数是否被称为 first ,以摆脱无用的临时对象(不是它需要的),但我已经实际使用了那种declare-now-assign-later模式无处不在,从未遇到过这样的问题。

6 个答案:

答案 0 :(得分:8)

您缺少一个赋值运算符。请记住rule of three(或五个)。

问题大致如下:

T t; // default constructed t
t = T(2); // T(2) constructor with a single argument, assignment operator= called with this == &t

您没有提供赋值运算符,因此临时的指针值只是复制到t中,然后在临时的析构函数中删除指向的内存。

另外:如果构造的对象无效,则没有默认构造函数。

答案 1 :(得分:6)

如果您的对象具有任何用户定义的构造函数,则使用构造函数构造始终。仅定义一个没有任何构造函数参数的对象使用默认构造函数,而不管该对象之后是否被覆盖。那是

FixedByteStream received;

将调用默认构造函数。下一行更有趣:

received = FixedByteStream(12);

此行创建一个带有参数FixedByteStream的临时12。在内部,这将分配一些内存,但由于临时表在完整表达式的末尾被破坏(在这种情况下基本上是在分号到达时),你不会做很多好事。构建此临时对象后,使用自动生成的复制分配将其分配给received,如果您手动编写它,它将看起来像这样:

FixedByteStream& FixedByteStream::operator= (FixedByteStream& other) {
    this->existing = other.existing;
    this->size     = other.size;
    this->address  = other.address;
    return *this;
}

也就是说,一旦执行了这个赋值,就必须使用相同的FixedByteStream副本,其中一个副本即将被销毁,并将释放刚分配的资源。这显然不是你想要的,即你肯定需要实现复制赋值运算符,以使你的类表现良好。一般来说,析构函数的存在可以做任何有趣的事情,这是一个很好的提示,你也需要一个赋值运算符。实际上,还有另一个生成的操作,即复制构造函数,它大致执行复制赋值所做的操作,只是它复制构造成员而不是分配它们。这也不是你想要的课程。

现在有趣的问题变成:如何解决FixedByteStream?实际上,您需要使用引用计数来跟踪当前正在查看FixedByteStream的对象数量,分配内容的副本,或者您需要使用移动语义支持(也称为右值引用)仅适用于C ++ 2011。除非你真的知道自己在做什么,否则我建议你在所有情况下复制流,并为以后留下更高级的方法。

答案 2 :(得分:4)

一步一步:

//create a new object using the default constructor
//I don't see why you think it's stupid that the constructor is called
//it's doing what you're telling it to do
FixedByteStream received;

//FixedByteStream(12) creates a temporary object
//you then copy this object in the received object you already have
//you're using the default operator =
//the temporary object is deleted after it is copied to received
received = FixedByteStream(12);

//received is a valid object
received[0] = 't';

编辑:

正如我看到这个问题有很多错误的答案,我会进一步解释。我可能会对此感到讨厌,但这是一个非常重要的概念,我贬低了,所以错误的答案不会被接受并被认可。

你基本上是在堆栈上初始化一些对象。

我会简化你的案子:

class A
{
    A() {}
    A(const A& other) {}
    A& operator = (const A& other) {}
};

我们来谈谈范围:

{ //begin scope
  A a;  //object a is created here
        //default constructor is called
} //a is destroyed here
  //destructor is called

到目前为止一切顺利。

现在,分配:

{
   //two objects are created with default constructor
   A a;
   A b;
   //object b is assigned to a
   //operator = will be called
   //both objects are still alive here
   a = b;
   //...
} // a and b will be destroyed, destructor called

到最后一部分:

{
   A a;
   a = A();
}

几乎相当于:

{
   A a;
   {
      A b;
      a = b;
   }
}

当您致电a = A()时,A()会创建一个临时对象,该对象已分配给a然后销毁。

因此,简化中的对象b是被销毁的临时对象。不是a,原来的a仍然有效。

不是赋值运算符声明。如果未定义一个,则使用默认值。在这种情况下,你可能想写自己的。

答案 3 :(得分:1)

FixedByteStream(12)通过默认运算符=分配给received。请注意,您没有使用FixedByteStream(12)在堆中分配new,而是在本地范围内分配它,而不指定保存它的变量的名称。

您的代码有点类似于:

FixedByteStream received;
FixedByteStream temp(12);
received = temp;
received[0] = 't';

仅在我的示例temp中有一个名称,其范围是整个函数,并且在您的测试代码temp中没有名称,它仅存在一行,然后被销毁

您创建的FixedByteStream(12)对象无法在此行之后使用,因为它甚至不是命名变量。

答案 4 :(得分:0)

对象已在此行初始化

FixedByteStream received;

在线

received = FixedByteStream(12);

您重新初始化它。正确的方法是:

FixedByteStream received(12);
// Or
FixedByteStream *received;
received = new FixedByteStream(12);

(我绝对会选择第一个)

答案 5 :(得分:-1)

您似乎无法理解对象生命周期,并错误地将此代码解释为Java代码。

当您编写FixedByteStream received;时,使用无参数构造函数创建FixedByteStream类型的对象。当您编写received = FixedByteStream(12);时,会创建另一个对象,调用=运算符并解析新创建的对象。

你也没有覆盖operator =所以对象是按字节复制的,这是不正确的。