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

Dig*_*Man 8 c++ constructor destructor class

我有以下课程:

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;
};
Run Code Online (Sandbox Code Playgroud)

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

FixedByteStream received;
received = FixedByteStream(12);
received[0] = 't';
Run Code Online (Sandbox Code Playgroud)

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

那么,它为什么被称为那里?我可以在某种程度上理解是否首先调用了析构函数,以摆脱无用的临时对象(不是它需要的),但我几乎无处不在地使用了那种declare-now-assign-later模式而且从未遇到过这样的问题之前.

pmr*_*pmr 10

您缺少一个赋值运算符.记住(或五)的规则.

问题大致如下:

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

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

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

  • @DigitalMan您的代码基本上是三规则的默认示例.但是在现代C++代码中,这种情况非常罕见.通常用你的例子写一个`vector`,这是不必要的. (2认同)

Die*_*ühl 6

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

FixedByteStream received;
Run Code Online (Sandbox Code Playgroud)

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

received = FixedByteStream(12);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Luc*_*ore 5

一步步:

//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';
Run Code Online (Sandbox Code Playgroud)

编辑:

我看到这个问题有很多错误的答案,我会进一步解释。我可能对此有些讨厌,但这是一个非常重要的概念,我投了反对票,因此错误的答案不会被接受并被视为理所当然。

您基本上是在初始化堆栈上的某个对象。

我会简化你的情况:

class A
{
    A() {}
    A(const A& other) {}
    A& operator = (const A& other) {}
};
Run Code Online (Sandbox Code Playgroud)

让我们谈谈范围:

{ //begin scope
  A a;  //object a is created here
        //default constructor is called
} //a is destroyed here
  //destructor is called
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。

现在,分配:

{
   //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
Run Code Online (Sandbox Code Playgroud)

进入最后一部分:

{
   A a;
   a = A();
}
Run Code Online (Sandbox Code Playgroud)

几乎等同于:

{
   A a;
   {
      A b;
      a = b;
   }
}
Run Code Online (Sandbox Code Playgroud)

当您调用 时a = A()A()会创建一个临时对象,该对象被分配a然后销毁。

所以对象b在我的简化中是被破坏的临时对象。不是a,您的原件因此a仍然有效。

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