我对构造函数和析构函数的技术原因并不熟悉。我编写了一个程序,其中的函数显示了对象的起始状态,结束状态,内存位置,值,以及何时调用构造函数和析构函数。
我无法理解为什么他们被召唤以及他们实际在做什么。我将发布测试运行的结果和我正在使用的代码 - 以及我所知道的大声笑。
结果:
Constructor called on 0x7fffc053f070
Initial state:
Location: 0x7fffc053f070
Value: 0
--- FOO ----
Location: 0x7fffc053f070
Value: 1
--- BAR ----
Location: 0x7fffc053f080
Value: 2
Destructor called on 0x7fffc053f080
--- BAZ ----
Location: 0x7fffc053f070
Value: 2
Final state:
Location: 0x7fffc053f070
Value: 2
Destructor called on 0x7fffc053f070
CODE:
#include <iostream>
#include <vector>
using namespace std;
//Short memory addresses are on the heap
//Long memory addresses are on the stack
class A {
public:
A(){
m_iValue = 0;
cout << "Constructor called on " << this << endl;
}
/* A(const A & a){
m_iValue = a.m_iValue;
cout << "Copy constructor called on " << this << endl;
}
*/
void increment(){
m_iValue++;
}
void display(){
cout << "Location: " << this << endl;
cout << " Value: " <<m_iValue << endl;
cout << endl;
}
virtual ~A(){
cout << "Destructor called on " << this << endl;
}
private:
int m_iValue;
};
void foo(A & a){
a.increment();
a.display();
}
void bar(A a){
a.increment();
a.display();
}
void baz(A * a){
a->increment();
a->display();
}
void blah(vector<A*> vA){
vA.back()->display();
delete vA.back();
vA.pop_back();
}
int main(int argc, char * argv[]){
A a;
cout << "Initial state: " << endl;
a.display();
cout << endl;
foo(a);
bar(a);
baz(&a);
cout << endl;
cout << "Final state: " << endl;
a.display();
return 0;
}
我认为发生了什么:
因此,构造函数被调用一次,析构函数被调用两次。在main中创建对象时,将调用构造函数。在foo()中,m_iVariable通过引用传递,并且函数为内存中该位置处的对象增加m_iValue。因此程序将值显示为1(从0开始递增)。
这是我感到困惑的地方..第三个位置与前两个位置不同。该对象将直接传递给bar()。我不明白在不调用构造函数的情况下位置如何不同,或者为什么在递增后调用析构函数,使值为2.
但巴兹也增加了价值。这意味着酒吧实际上什么都没做?我仍然不知道bar如何显示新的内存位置和析构,但从不构造。
抱歉所有文字,但任何事情都会有所帮助。谢谢!
哦,注释掉的代码,以及函数blah用于其他事情,对于这个问题并不相关。
答案 0 :(得分:3)
当您将值传递给bar()
时,您正在创建一个新对象,该对象是该函数的本地对象。 bar()
返回时,该对象被销毁。它由复制构造函数A(A const &)
初始化,由于您没有自己声明,因此隐式生成了它。如果您取消注释复制构造函数,那么您将看到它发生。
通常,在允许复制具有非平凡析构函数的对象时必须小心。此类通常管理析构函数中释放的资源,您必须注意副本不会尝试管理相同的资源。在制作类似的课程时,请始终记住Rule of Three。
答案 1 :(得分:2)
一般来说,当您通过在main
例程中创建对象时隐式初始化对象,或者使用{{1}显式分配和初始化对象时,将调用构造函数。 }。重要的是要注意,通过值将参数传递给方法可以使用复制构造函数创建对象的副本,这是一种初始化。
当堆栈上分配的对象超出范围,或者使用new
释放动态分配的对象时,将调用析构函数。
你在这里看到两个析构函数调用,因为你的一个方法是按值传递的,并且创建了它的副本,并且稍后在方法完成时销毁它。这两个对象的内存地址不同。
如果您通过了值,则对副本所做的任何修改都不会反映在原件中。这就是为什么在许多C ++应用程序中,方法通过引用传递事物,如delete
允许修改,或通过 const 引用使其清除no允许更改foo(A& a)
。指针有时用于相同的目的。
你在这里遇到问题的原因是因为你不知道关于析构函数,复制构造函数和复制赋值运算符的Rule of Three。
答案 2 :(得分:0)
首先,输出显示不同数量的构造函数和析构函数调用的原因是它不显示所有此类调用。在您的情况下,您尝试检测代码失败,而不是考虑所有构造函数。通常它也可能由构造函数抛出异常引起,因为它只是与析构函数调用数相匹配的成功构造函数cakks的数量,并且它可能是由未被销毁的对象引起的(例如,动态分配)并且没有删除)。
忽略(ab)使用该语言的极低级功能,C ++规则旨在确保
对于T类型的每个对象,只有一个T构造函数调用,它在任何其他调用之前发生。
对于每个构造函数调用成功,都会有相应的析构函数调用(如果对象被销毁)。
这些规则以递归方式保存在对象的层次结构中。具有子对象(数据成员)的对象是这种层次结构的示例。子对象可以是基类子对象,而不是数据成员。
构造函数的职责是在技术上将一段原始内存转换为一个类型化且有意义的对象(使用复制赋值运算符来约束它,该运算符将现有值替换为新值,可能会释放先前分配的内存)。在设计层面,构造函数的主要职责是建立类不变,无论您如何对公共方法调用之间的对象状态进行假设。析构函数的工作是清理,例如释放资源。
从语言的角度来看,当构造函数抛出异常时,构造失败。
new
- 表达式在内存分配和构造之间提供了类似事务的非常强大的耦合。它允许您提供两组参数:分配函数的第一组参数,以及构造函数的第二组参数。除了下面提到的情况之外,如果构造函数失败,则会自动释放内存并传播异常。即一般情况下,要么两者都成功,要么撤消任何副作用,并通知调用代码失败。
没有进行清理的唯一例外是您定义了自定义分配函数,即所谓的“placement new”运算符,并且无法提供相应的释放函数。我不知道为什么需要自定义释放函数,实际上,这是它被隐式调用的唯一情况。
答案 3 :(得分:0)
在方法bar(A a)
中,调用的是复制构造函数A(const A& a)
。如果您尚未声明它,则由编译器隐式创建,并为每个成员调用复制构造函数。
只需添加此方法:
A(const A& a){
m_iValue = a.value;
cout << "Copy constructor called on " << this << endl;
}
答案 4 :(得分:0)
简单地将print语句放在构造函数和析构函数中以查看它们是否在运行时运行它总是很有趣。上面有很多好的答案,所以我不会说什么,但这样做对我早期的编程生涯有所帮助。