问题引入
接下来我给出两种函数参数的传递方式,你是否能迅速且确定地说明二者的不同? 检测以下你是否知道这个知识点,你是否需要继续阅读这篇文章。
class A {};
void func(A a) {}
// 第一种方式
A a;
func(a);
// 第二种方式
func (A());
你看出来区别了吗?二者的在参数传递过程的区别有什么区别呢?
第一种和第二种方式分别调用了 A 类的那些构造函数呢?
分析
函数的参数传递过程,无非就是涉及到形式参数的构建、形参与实参的关系等等。
为了更好的监控函数参数的创建过程,我们首先写个简单类,检查类对象从创建到销毁的全部过程。
class B
{
public:
B() :data(0) { cout << "默认构造函数 " << endl; }
explicit B(int i) :data(i) { cout << "带参数的构造函数 " << endl; }
B(const B &b) { cout << "复制(拷贝)构造函数 " << endl; data = b.data; }
B(B&& b) {cout << "移动构造函数" << endl; data = b.data; }
B& operator = (const B &b) { cout << "赋值运算符的重载函数 " << endl; }
~B() { cout << "析构函数" << endl;}
int get() { return data; }
private:
int data;
};
接着,我们分别测试上面给出的两种函数调用方式,来观察一下变量的改变情况。
测试
测试一
void func(B b) // 定义测试函数
{
cout << b.get() << endl;
}
int main()
{
puts("方式一");
B b(23);
func(b);
puts("方式二");
func(B (1));
}
/*output
方式一
带参数的构造函数
复制(拷贝)构造函数
23
析构函数
方式二
带参数的构造函数
1
析构函数
析构函数
*/
方式一:先创建一个对象 b,然后传入到 func 中,会先调用构造函数创建 b , 然后调用函数时,调用一次拷贝构造函数,创建一个形参。
方式二:直接调用了构造函数,其他什么什么构造函数都没调用。说明我们在函数外创建的对象,直接放到函数内使用,中间没经过任何加工。相当于这个变量直接在函数内部构造。
方式二比方式一少调用一次拷贝构造函数和析构函数。
走到这一步显然是不够的,我们在测试一下其他函数。
测试二
void func2(B &b)
{
cout << b.get() << endl;
}
int main()
{
puts("方式一");
B b(23);
func2(b);
puts("方式二");
func2(B (1));
}
运行一下,发现报错,看看他的错误提示。
他说 B (1) 创建的是一个右值( rvalue ),而我们 func2 要的是左值引用,这肯定是不行。
B (1) 创建的是右值,是这样吗?我们来验证一下。
编写一个参数为右值引用的函数。
测试三
void func3(B&& b) {
cout << b.get() << endl;
}
int main()
{
puts("方式一");
B b(23);
func1(std::move(b));
puts("方式二");
func1(B (1));
return 0;
}
/*output
带参数的构造函数
移动构造函数
23
析构函数
方式二
带参数的构造函数
1
析构函数
析构函数
*/
函数正常运行。我们发现采用第二种方式少了一次移动构造和一次析构函数。
汇总
经过上面三次测试,我们发现,直接使用 func(B (1))
的方式总是比方式一少一次拷贝构造函数或者移动构造函数。
少调用一次拷贝构造函数和移动构造函数有什么用?
- 代码运行效率增加。
- 对于一些无法进行拷贝构造的类来说,这是相当重要的。
总结
如何理解 func(B(1)) ;
可以把函数展开来看。void func(B b); func(B(1)); 其实就是 对形参b 进行
func(B b = B(1));
,也就是func(B b(1));
,可不就是只进行一次构造函数的调用。
有何应用?
当我们往 vector 中 push_back 数据,交给vector 管理时,就不需要先创建对象,然后调用移动构造和拷贝构造函数在 vector 中在创建一个副本变量。
可以直解通过push_back(B(1))往vector中添加数据,少一次拷贝或移动构造。当然,使用emplace_back也能实现。
此时,你可能会想到:我不如直接创建函数,让函数的参数不是对象,而是构造对象所需要的数据元素,然后在函数内部构造对象不就行了吗?
当然可以。但是设想一下,如果系统提供了函数,且给定了函数参数的类型是对象,此时就派上用场。
总的来说,应用不是很广。但是呢,我们要理解,如果看到别人写了个 func(B(2));
的代码,我们要知道这行代码干了什么事?
相当于直接在函数内部创建一个对象。且 B(2) 创建的是临时对象,需要函数的参数类型为B或B&&来接收参数,不能是B&。