先看下面示例代码:
class CTest
{
public:
CTest()
{
cout << "CTest()" << endl;
m_nId = 10;
}
CTest(const CTest& objTest)
{
cout << "CTest(const CTest& objTest)" << endl;
m_nId = 200;
}
void PrintData()
{
cout << m_nId << endl;
}
private:
int m_nId;
};
CTest DoSomethingA()
{
CTest objTest;
return objTest;
}
CTest DoSomethingB()
{
return CTest();
}
void DoSomethingC(CTest objTest)
{
objTest.PrintData();
}
CTest objTest;
{
CTest t1(objTest);
CTest t2 = objTest;
CTest t3 = CTest(objTest);
}
对于这样的调用输出:
CTest()
CTest(const CTest& objTest)
CTest(const CTest& objTest)
CTest(const CTest& objTest)
这里涉及到双阶段转换
重写调用的每个定义,其中初始化操作被剥除
class CTest的copy constructor调用操作会被安插进去
VS编译器可能会做这样的转换:
{
// 初始化操作被剥除
CTest t1;
CTest t2;
CTest t3;
// 调用拷贝构造
t1.CTest::CTest(objTest)
t2.CTest::CTest(objTest)
t3.CTest::CTest(objTest)
}
{
CTest objTest;
DoSomethingC(objTest);
}
输出:
CTest()
CTest(const CTest& objTest)
200
C++ standard说,把一个class object当作参数传给一个函数或者是作为函数的返回值相当于这样的转换CTest objTest = arg;
,编译器实现技术上有一种策略是导入所谓的临时对象,并调用copy constructor将它初始化,然后将临时对象以引用的方式交给函数,
并在函数返回前调用destructor(如果有),所以上面的调用可能被编译器转换成类似下面这样:
CTest tmpObject;
// 调用拷贝构造
tmpObject.CTest.CTest(objTest)
void DoSomethingC(CTest& tmp)
DoSomethingC(tmpObject)
像上面代码示例提到的一个函数:
CTest DoSomethingA()
{
CTest objTest;
return objTest;
}
对于这个函数的调用:
{
CTest objTestA = DoSomethingA();
}
输出:
CTest()
CTest(const CTest& objTest)
这里涉及到一个编译器的另一种实现方式“拷贝建构(copy construct)”,它的方式是把实际参数直接建构在其应该的位置上,此位置视函数活动范围的不同,记录于程序堆栈中。上述调用分为两个阶段转化:
对函数DoSomethingA加上额外参数,类型是CTest object 的引用。这个参数将用来放置“拷贝建构(copy construct)”而得的返回值
在return之前安插copy constructor调用操作,以便将欲传回的object的内容当作上述新增参数的初值
对应编译器转化后代码可能是这样:
void DoSomethingA(CTest& objTest)
{
CTest tmpTest;
tmpTest.CTest::CTest();
objTest.CTest::CTest(tmpTest);
return;
}
对于程序中调用DoSomethingA的地方可能做出的转化:
{
// 初始化操作被剥除
CTest objTestA;
DoSomethingA(objTestA);
}
上面的代码示例提到的这个函数:
CTest DoSomethingB()
{
return CTest();
}
对应编译器转化后代码可能是这样:
void DoSomethingB(CTest& objTest)
{
objTest.CTest::CTest();
return;
}
调用原型:
{
CTest objTestB = DoSomethingB();
}
编译器转化后:
{
// 初始化操作被剥除
CTest objTestB;
DoSomethingB(objTestB);
}
对应这样的转化结果,也就可以解释了调用DoSomethingB输出的结果:
CTest()
对于上面提到的代码示例编译器优化的程度视不同的编译器而定,上面提到的对copy constructor的调用要视不同编译器而定,可以在目标编译器上 运行后才能知道对copy constructor的调用是不是你所期望的。例如有些时候你可能就希望调用copy constructor。这时就要做相应的测试才能得知。
关于编译器的优化不做讨论,就像上面的代码示例:
CTest DoSomethingA()
{
CTest objTest;
return objTest;
}
被称为NRV优化技术,而编译器对于NRV技术是否支持和什么时候使用NRV技术等等以后讨论
对于我们上面提到的代码示例,没有必要提供copy constructor,而copy constructor也被编译器视为trivial(关于copy constructor的 copy constructor的介绍可以参考这篇文章),当发生copy时会触发高效的”bitwise copy”操作,而需要大量的memberwise copy时, 那么提供显式的copy constructor是有必要的(不只有这一个场景)。对于显式的copy constructor里执行copy的手段也有不同,可以逐一赋值、可以借助memset、memcpy等等,当使用memset、memcpy时有可能破坏编译器产生的内部members,例如class声明一个或多个virtual functions,或者内含virtual base class,那么使用memset、memcpy时都会破坏编译器产生的内部members初值
copy constructor 的应用迫使编译器或多或少对程序代码做转化,从上面的例子,发现尤其当一个函数以by value的方式传回class object,不论这个class显示定义copy constructor还是合成的,都会导致转化,具体的转化还要针对具体的编译器测试而定。多一些测试 对于何时调用default constructor、何时调用copy constructor做到心中有数,写出的代码才可以按照我们预想的逻辑运行