学生大本营中有同学提出这个问题,感觉有一定代表性,这里做个回答。
一家之言哈,欢迎拍砖。
问题:
C++中当函数返回值类型时,系统会默认调用拷贝构造函数将返回值复制到函数作用域外,然后销毁超出作用域的对象,再将作用域外的临时对象拷贝到接收对象中。基于此问题,在下一直以为,不应该依靠“隐式”的行为。而数周之前在《C++编程思想》中不慎找到了解决该问题的理论,但由于其理论过于合理以致代码测试拖到了日前进行,目前的问题在于:基于我所理解的该书中描述的理论,我所设计的代码并不能正确运行,因此想在此请教各位。
根据记忆,该书中出现的理论大概是这么个意思:
struct X
{
int num;
};
X fun (const X &test)
{
X copy;
copy.num=test.num;
return copy;
}
在这里书上指出了这个fun函数存在性能损失:在函数作用域内建立了一个对象,在超出作用域之后它必须被销毁,而在返回类型值的时候又必须调用一次拷贝构造函数以建立函数作用域外的临时对象,如此一来增加了无谓的性能损失。问题的关键在于,书上说可以将代码改写成如此形式提高性能:
X fun (const X &text)
{
return X(test);
}
一旦改成如此形式,由于构造函数位于return语句中,那么此代码的效果将指示编译器直接将临时对象创建在函数作用域之外而省去了一次对象的创建和销毁工作。
struct X
{
int num;
};
X fun (const X &test)
{
X copy;
copy.num=test.num;
return copy;
}
在这里书上指出了这个fun函数存在性能损失:在函数作用域内建立了一个对象,在超出作用域之后它必须被销毁,而在返回类型值的时候又必须调用一次拷贝构造函数以建立函数作用域外的临时对象,如此一来增加了无谓的性能损失。问题的关键在于,书上说可以将代码改写成如此形式提高性能:
X fun (const X &text)
{
return X(test);
}
一旦改成如此形式,由于构造函数位于return语句中,那么此代码的效果将指示编译器直接将临时对象创建在函数作用域之外而省去了一次对象的创建和销毁工作。
现在进入主题:如果按照这个理论,那么我认为改写之后的函数是属于显式调用拷贝构造函数而将临时对象创建在了函数作用域外,也就是说,如果此理论成立,那么即使我将拷贝构造函数声明为explicit的,那么测试这种机制的代码也应该能通过编译并运行——我显式地调用了拷贝构造函数传值。但是,从我测试的代码来看,即使使用第二个版本的函数也还是会隐式地调用拷贝构造函数。
那么我想问的是,如何在不使用引用和指针,也不使用包装器函数的情况下,在函数返回值类型的过程中消除构造函数的隐式调用?
具体一些的话就是:如果我将拷贝构造函数声明为explicit的,不使用引用和指针,也不使用包装器,如何设计返回值类型的函数?
我使用的编译器是gcc,IDE是DEVCPP 4.9.9.2 。
我的回答:
这个问题很怪异,我觉得甚至不是一个问题。
首先,一般说来,我的习惯,不会使用struct的C++特性,如果要定义类,用class更好一点。struct用来存放纯数据较好。
一般不建议返回复杂的函数返回值,比如你文中的例子,一定要返回一个对象,我的习惯是不要这么做。
在C和C++语言中,我一般习惯于区分简单变量和复杂变量,int这类基本类型属于简单变量,系统可以默认拷贝,处理就很简单,而对象,结构体实例一般数据复杂变量,一般不建议使用函数返回值形式默认返回。一般是单独构建一个Copy函数,来针对性做拷贝动作,避免出错。
原则很简单,就是不要在函数中向外返回复杂变量,里面太多隐式操作,不直观,不明显。给自己造成潜在bug的可能性,就不划算了。
因此,上述拷贝构造函数,我其实在工程中,一次都没有用过,这么多年也算把饭钱赚到手了,呵呵。
至于你说的性能损失,我要说有点吹毛求疵了。事实上,基于编译器的性能损失,我一般不考虑,因为实际工程中,有时候,换个算法,早就找补回来了。如果一个系统会对这点性能损失敏感,首先说明的是这个系统有问题,需要改系统设计。
因此,除了做游戏时,有时候高速图形刷屏,对编译器级的性能敏感外,一般工程应用,都不考虑这个问题。
我们考虑更多的,反而是架构的合理性。多一个无谓的节点,拓扑角色,有时候带来的性能的损失更大。
一般说来,就语言谈语言,而不是和具体工程应用结合着讨论,一般性能意义不大。
首先,一般说来,我的习惯,不会使用struct的C++特性,如果要定义类,用class更好一点。struct用来存放纯数据较好。
一般不建议返回复杂的函数返回值,比如你文中的例子,一定要返回一个对象,我的习惯是不要这么做。
在C和C++语言中,我一般习惯于区分简单变量和复杂变量,int这类基本类型属于简单变量,系统可以默认拷贝,处理就很简单,而对象,结构体实例一般数据复杂变量,一般不建议使用函数返回值形式默认返回。一般是单独构建一个Copy函数,来针对性做拷贝动作,避免出错。
原则很简单,就是不要在函数中向外返回复杂变量,里面太多隐式操作,不直观,不明显。给自己造成潜在bug的可能性,就不划算了。
因此,上述拷贝构造函数,我其实在工程中,一次都没有用过,这么多年也算把饭钱赚到手了,呵呵。
至于你说的性能损失,我要说有点吹毛求疵了。事实上,基于编译器的性能损失,我一般不考虑,因为实际工程中,有时候,换个算法,早就找补回来了。如果一个系统会对这点性能损失敏感,首先说明的是这个系统有问题,需要改系统设计。
因此,除了做游戏时,有时候高速图形刷屏,对编译器级的性能敏感外,一般工程应用,都不考虑这个问题。
我们考虑更多的,反而是架构的合理性。多一个无谓的节点,拓扑角色,有时候带来的性能的损失更大。
一般说来,就语言谈语言,而不是和具体工程应用结合着讨论,一般性能意义不大。