C++对象销毁

7
我正在从Java转向C++,并且正在尝试理解对象的构造/析构。在Java中,当我执行以下操作时:
Myclass c=createANewObject();
c=createANewObject();

“旧的C被垃圾收集,另一个同名的变量被创建。如果我尝试在C++中做同样的事情,我会得到奇怪的行为。”
class my_class 
{
string content;
time_t t;
public: 
my_class(string c);
~my_class();
};

my_class::my_class (string c) 
{
content=c;
cout<<"Init -" << content << "-" << t <<endl;
}

my_class::~my_class()
{
cout<<"Destroyed -" << content << "-" << t <<endl;

}

my_class get_new_object(string s)
{
   my_class c(s);
   return   c;
}

int main()
{
    my_class c=get_new_object("A");
    c=get_new_object("B");
}

不是得到

Init -A-
Init -B-
destr.A
destr.B

因为首先我创建A,然后创建B,接着A被销毁,作用域结束,所以B被销毁。
我得到的输出结果是:
创建-A-1456178128 创建-B-1456178131 销毁-B-1456178131 销毁-B-1456178131
所以我的A被创建但没有被销毁,而B则被错误地销毁了两次?

3
为什么您认为旧的 A 应该被摧毁?该对象仍在相同的作用域内,您只是给它赋了一个新值。 - StoryTeller - Unslander Monica
为什么B被销毁了两次?创建时间表明A被创建并且从未被销毁,而B被创建并且被销毁了两次。 - Ciccio Pasticcio
6
不要将Java的垃圾收集与C++的确定性析构范式进行比较,它们是完全不同的东西。在Java中,我们不知道对象什么时候真正被销毁,只知道它会在某个时间被销毁。而在C++中,我们确切地知道对象何时被销毁。 - PaulMcKenzie
1
你在主函数中将B分配给了对象。这就是为什么第二次看到它的原因。你的对象不是AB,而是c和一个临时变量。 - StoryTeller - Unslander Monica
3
这听起来像是复制省略返回值优化 - callyalater
显示剩余4条评论
3个回答

8
在Java中,你的代码执行以下顺序:
- 创建一个新对象 - 将引用 c 设置为引用该对象 - 创建另一个新对象 - 释放旧对象的引用 c 并使其引用新对象 - 旧对象现在没有引用,并将在稍后进行垃圾收集
在C ++中,您的代码有所不同。 不要被类似的语法所迷惑。在C ++中,您可以使用不同的语法执行与Java代码几乎相同的步骤。但是,您实际上使用的语法执行以下操作:
- 创建对象 get_new_object :: c(“A”) - 返回该对象的副本 - 销毁 get_new_object :: c - 通过复制返回的副本来初始化对象 main :: c - 销毁返回的副本 - 创建对象 get_new_object :: c(“B”) - 返回该对象的副本 - 销毁 get_new_object :: c - 通过复制从返回的对象中获得详细信息来更新 main :: c - 销毁返回的对象 - (在main结束时)销毁 main :: c 上述某些复制可能会被优化掉,这个过程称为复制省略。如果您使用编译器开关禁用复制省略,则应看到所有上述步骤,即5个析构函数,2个普通构造函数以及(如果您还添加其他特殊函数的输出)3个复制构造函数和1个赋值运算符。
注:在C ++ 11中,临时对象可能会被移动进出(如果编译器决定不使用省略),而不是复制。但我将其省略以保持列表简单。

第三步:销毁 get_new_object::c。为什么我看不到内部 C 对象的 "destroyed A"? - Ciccio Pasticcio
2
我在这里使用::作为伪代码(实际上你不能以这种方式引用局部变量) - M.M
@CiccioPasticcio 这是一个可以通过复制省略删除的对象之一。使用复制省略,第一次调用 my_class c(s); 将直接在 main 的 c 的内存区域中构造对象,绕过了 my_class::c 和返回值对象。但是这不会发生在第二次调用中,因为它没有用结果初始化对象。(c 仍然被省略了第二次,但返回值对象没有被省略,这就是你看到的其中一个析构函数) - M.M
现在我明白了。M.M. 你是最棒的! - Ciccio Pasticcio

1
这有点特定于编译器和版本(C++11)。 get_new_object创建一个条目,并返回它的值,这可能会创建3个对象、2个对象或1个对象。
3个对象:
编译器创建一个堆栈对象。这将在临时对象中获得复制构造函数。这个临时对象使用另一个复制构造函数移动到C中。
2个对象:
更智能的编译器会失去中间的临时对象。
1个对象:
非常智能的编译器意识到只有一个结果很重要。c++11通过移动构造函数来帮助。
复制/移动构造函数将被创建,但不会在您的代码中报告。
析构函数准确描述了创建的对象数(2)
a/b难题:
编译器发现只需要A。因此。
 init A

当创建b时,会生成一个新对象。这个对象会被复制构造到 c 中。
Init b

然后b的临时变量被销毁。
Destr b

然后 c 被销毁。
destr b

1
    my_class get_new_object(string s)
    {
    my_class c(s);
    return  c;
    }

    int main()
    {
    // Sequence of events:
    //    - get_new_object() called
    //    - Inside get_new_object() "my_class c() instance created on stack and constructor called --> This is the first constructor call you see
    //    - Inside get_new_object() "return c;" statement  first creates a copy of 'c' and assigns to the my_class c in main() --> This is your second constructor call
   //     - return c; initiates destruction of the my_class c which was created inside get_new_object() since it's on the stack and the function is going out of scope  --> This is your first Destructor call
   //     - main ends --> This is when the my_class c of main() goes out of scope and the destructor is called again.
    my_class c=get_new_object("A");
    c=get_new_object("B");
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接