- 作者: 雪山肥鱼
- 时间:20210824 18:44
- 目的: 程序的语义转化
编译器对代码拆分
class X
{
public:
int m_i;
X(const X & tmp)
{
m_i = tmp.m_i;
cout << "拷贝构造函数被调用" << endl;
}
X()
{
m_i = 0;
cout << "构造函数被调用" << endl;
}
};
int main(int argc, char ** argv)
{
X x0;
x0.m_i = 15;
X x1 = x0;
X x2(x0);
X x3 = (x0);
/*
编译器会拆分成2个步骤
X x100 = x0;
X x100; //步骤一: 定义一个对象,为对象分配内存,从编译器视角来看,这句是不调用X类的构造函数
x100.X::X(x0); //步骤二: 直接调用对象的拷贝构造函数。
*/
return 0;
}
代码运行结果:
1次构造函数, 3次拷贝构造函数。
对于 X x100 = x0 来看。
编译器会拆成2步。 流程如上。
函数的参数带对象
class X
{
public:
int m_i;
X(const X & tmp)
{
m_i = tmp.m_i;
cout << "拷贝构造函数被调用" << endl;
}
X()
{
m_i = 0;
cout << "构造函数被调用" << endl;
}
~X()
{
cout << "析构函数被调用" << endl;
}
};
void func(X tmp)
{
return;
}
int main(int argc, char ** argv)
{
X x0;
func(x0);
return 0;
}
结果:

拷贝构造,给的是 函数参数 X tmp。
第一次析构给的也是 函数参数 X tmp
第二次给的是 main 中的 x0
返回值带对象

拷贝构造函数发生于 return x0, x0 会拷贝给一个临时对象。F9 调试就知道了。
析构给一个给的 函数里的,一个给的main 函数中的X my
class X
{
public:
int m_i;
X(const X & tmp)
{
m_i = tmp.m_i;
cout << "拷贝构造函数被调用" << endl;
}
X()
{
m_i = 0;
cout << "构造函数被调用" << endl;
}
~X()
{
cout << "析构函数被调用" << endl;
}
void functest()
{
cout << "functest()" << endl;
}
};
X func()
{
X x0;
return x0;// 执行完毕后有一个拷贝构造函数 和一个析构函数, X tmp = x0 ,析构函数,析构了x0
}
/*
void func(X & extra)
{
X x0;
extra.X::X(x0);
return;
}
*/
int main(int argc, char ** argv)
{
X my = func();//这里倒是没有拷贝构造函数
/*
X my
func(my)// 修改了my的值
*/
func().functest();
/*
X my;
(func(my), my).functest();//逗号表达式: 先计算表达式1, 再计算表达式2,最后调用functest
*/
X(*pf)();
pf = func;
pf().functest();
/*
X my;
void (*pf)(X&);
pf = func;
pf(my);
my.functest();
*/
return 0;
}
- 对于 X my = func(), 编译器会将func 进行改造。
所以我们从程序员视角看到了一次 函数中 的拷贝构造,析构了x0, 析构了my - 对于 func().funtest() 的解读
其实我觉得这里没必要研究太深,反而会把自己绕进去。没必要。了解就好
简单优化
class CTempValue
{
public:
int val1;
int val2;
public:
CTempValue(int v1 = 0, int v2 = 0) :val1(v1), val2(v2)
{
cout << "调用了构造函数" << this<<endl;
cout << "val1 =" << val1 << endl;
cout << "val2 =" << val2 << endl;
}
CTempValue(const CTempValue &t) : val1(t.val1), val2(t.val2)
{
cout << "调用了拷贝构造函数" << this << endl;
}
virtual ~CTempValue()
{
cout << "调用了析构函数" << this << endl;
}
};
CTempValue Double(CTempValue &ts)
{
/*
显然会多调用一次构造函数,拷贝构造函数和 析构函数
*/
CTempValue tmpm;
tmpm.val1 = ts.val1 * 2;
tmpm.val2 = ts.val2 * 2;
return tmpm;
}
int main(int argc, char ** argv)
{
CTempValue ts1(10, 20);
//Double(ts1);// 没有东西去接,就是临时对象, 这行结束,临时对象析构
CTempValue ts2 = Double(ts1); // 不会多调用一次临时对象的析构
//ts1 = Double(ts1);// 会调用一次 析构, 析构临时对象
return 0;
}
函数Double 是有优化空间的。
同时,以上代码 析构顺序如下:

另外再填一句:看main 函数中的注释部分。用 ts2 和 ts1 去接的话,析构函数调用次数会有不同。
ts1 去接会明显多一次析构,析构的是从函数中返回的临时对象
Double 函数明显有缺陷,会多调用一次构造 + 拷贝构造(临时对象) + 析构
- 对 Double 函数简单优化
CTempValue Double(CTempValue &ts)
{
return CTempValue(ts.val1, ts.val2);//生成一个临时对象
}
只会调用一次 构造函数
- linux 平台
针对未优化的Double 函数,会自动优化。
关闭优化
g++ -fno-elide-constructors *.cpp
测试
clock_t start, end;
start = clock(); //以毫秒为单位
for(int i = 0; i < 100000; i++)
{
Double(ts1);
}
end = clock();
cout<<end - start<<endl;
测一下就知道了。大概有50ms的差距
linux 与 windows 对于拷贝函数的优化
class X
{
public:
int m_i;
X(const X & tmp)
{
m_i = tmp.m_i;
cout << "拷贝构造函数被调用" << endl;
}
X()
{
m_i = 0;
cout << "缺省构造函数被调用" << endl;
}
X(int value) :m_i(value)
{
cout<<"X(int) 构造函数被调用"<<endl;
}
~X()
{
cout << "析构函数被调用" << endl;
}
};
int main(int argc, char **argv)
{
cout << "-----------begin---------------" << endl;
X x10(1000);
cout << "--------------------------" << endl;
X x11 = 1000;//涉及隐式转换, 加上explicit 就不行啦
cout << "--------------------------" << endl;
X x12 = X(1000);//vs2017 把拷贝构造了优化le ?
cout << "--------------------------" << endl;
X x13 = (X)1000;
cout << "-----------end---------------" << endl;
return 0;
}
- windows:
除了第一个,余下3个全部调用的都是 X(int) 构造函数,而非拷贝构造函数 -
linux
如果关闭优化选项,全部都只调用 X(int) 构造函数
调用的是拷贝构造函数
linux.png
从编译器角度看如下:
/*
编译器角度 看 X12 = X(1000);
X _tmp;
_tmp.X::X(1000);
X x12;
x12.X::X(_tmp);//拷贝构造函数被调用
_tmp0.X::~X();//调用析构
*/
拷贝构造函数必须吗?
如果不涉及 指针的话,仅简单类型,则不需要,因为类本身支持 bitwise(按位拷贝)
class X
{
public:
int m_i;
X()
{
m_i = 0;
cout << "缺省构造函数被调用" << endl;
}
X(int value) :m_i(value)
{
cout << "X(int) 构造函数被调用" << endl;
}
~X()
{
cout << "析构函数被调用" << endl;
}
};
int main(int argc, char **argv)
{
X x0;
x0.m_i = 150;
X x1(x0);
cout << x1.m_i << endl;
return 0;
}
则不会调用拷贝构造函数,但依旧会得到正确的值
何时需要拷贝构造
深拷贝,否则会造成重复析构
class X
{
public:
int m_i;
int *p_m;
X(const X* tmp)
{
//增加拷贝构造函数会使得 bitwise 失效
p_m = new int(0);
memcpy(p_m, tmp->p_m, sizeof(int));
m_i = tmp->m_i;
cout << "拷贝构造函数被调用" << endl;
}
X()
{
m_i = 0;
p_m = new int(100);
cout << "缺省构造函数被调用" << endl;
}
X(int value) :m_i(value)
{
cout << "X(int) 构造函数被调用" << endl;
}
~X()
{
delete p_m;
cout << "析构函数被调用" << endl;
}
};
int main(int argc, char **argv)
{
X x0;
x0.m_i = 150;
X x1(x0);
//非常明显的 两个对象重复析构了 一片内存。所以才需要引入拷贝构造函数
cout << x1.m_i << endl;
return 0;
}
网友评论