【问题标题】:Why is this destructor being called immediately after creation?为什么在创建后立即调用此析构函数?
【发布时间】:2012-02-10 22:13:05
【问题描述】:

我有以下课程:

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 模式到处都是,以前从未有过这样的问题。

【问题讨论】:

  • 不一定是您的问题,但您需要添加复制构造函数。当前的复制构造函数最终会复制指针 - 因此首先调用的析构函数将删除其他对象的内存。
  • @Anycorn 在这种情况下他需要一个赋值运算符
  • 对 - 错过了那部分 - 它只是真正突出的东西

标签: c++ class constructor destructor


【解决方案1】:

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

您的代码有点类似于:

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

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

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

【讨论】:

  • 你确定它使用的是复制构造函数而不是默认的operator =
【解决方案2】:

您似乎不了解对象生命周期,并错误地将这段代码解释为 Java 代码。

当您编写FixedByteStream received; 时,将使用无参数构造函数创建一个FixedByteStream 类型的对象。而当你写received = FixedByteStream(12);时,另一个对象被创建,=操作符被调用,新创建的对象被销毁。

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

【讨论】:

  • 这是错误的。前一个对象不会被破坏,临时对象是,在它被复制到前一个对象之后。
  • 另外,在这种情况下 operator= 就像按位复制只是一个巧合。事实上,它调用了每个成员的赋值。
【解决方案3】:

一步一步:

//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 仍然有效。

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

【讨论】:

  • 其他答案似乎已修复。
  • 这是非常有用的信息,谢谢你的详细解释!
【解决方案4】:

对象已经在这一行初始化

FixedByteStream received;

在线

received = FixedByteStream(12);

你重新初始化它。正确的做法是:

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

(我肯定会选择第一个)

【讨论】:

  • 不幸的是,第一个是不可能的,因为在 practice 中,在创建对象时大小是未知的(范围问题)。第二个可能会起作用,但无论如何我真的应该设置复制构造函数和赋值运算符。
【解决方案5】:

您缺少一个赋值运算符。记住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 中,然后在临时的析构函数中删除指向的内存。

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

【讨论】:

  • 老实说,我没想到我需要为一个琐碎的副本覆盖它,谢谢你提供的信息。
  • @DigitalMan 您的代码基本上是三规则的默认示例。但是这样的情况在现代 C++ 代码中非常少见。您的示例通常会使用vector 编写,这是不必要的。
  • @DigitalMan:您本可以通过编写T t(2); 轻松绕过这一点。是什么让您想到将其分为两个步骤?
  • @KerrekSB 不知何故我很高兴他做到了。它暴露了一个错误并给他上了宝贵的一课。
  • @Kerrek SB 这是一个重现问题的例子。在我的实际代码中,它不知道 FixedByteStream 需要多大,直到范围更深几个级别 - 它具有可变大小是使其“固定”的原因。
【解决方案6】:

如果您的对象有任何用户定义的构造函数,则它总是使用构造函数构造。只定义一个没有任何构造函数参数的对象使用默认构造函数,而与该对象之后是否被覆盖无关。那是

FixedByteStream received;

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

received = FixedByteStream(12);

此行使用参数12 创建一个临时FixedByteStream。在内部,这将分配一些内存,但由于临时对象在完整表达式的末尾被销毁(在这种情况下,基本上是在到达分号时),这不会有什么好处。一旦构造了这个临时对象,它就会使用自动生成的复制分配分配给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 中可用。除非您真的知道自己在做什么,否则我建议您在所有情况下都复制流,并为以后留下更高级的方法。

【讨论】:

    猜你喜欢
    • 2016-03-02
    • 2014-09-18
    • 1970-01-01
    • 2021-03-30
    • 2011-12-13
    • 1970-01-01
    • 1970-01-01
    • 2018-11-19
    • 2021-08-04
    相关资源
    最近更新 更多