介绍
C++对象在作为函数参数以及返回值的copy策略一直是我困扰的一个问题,特别是在今天看到了C++11的新增加的特性std::move以及右值概念之后,激发了我的求知欲,决定把这一块详细的搞清楚。
测试对象代码
写了个简单的测试对象,在每个不同的构造函数以及析构函数中打印log
class A
{
public:
A()
{
cout << "construct" << endl;
}
A(const A& a)
{
member = a.member;
cout << "copy construct" << endl;
}
A(A&& a)
{
member = std::move(a.member);
cout << "rvalue copy construct" << endl;
}
~A()
{
cout << "destruct" << endl;
}
public:
string member;
};
测试传参
测试代码
// 参数传值;
void argumentObjectPassByValue(A a)
{
}
// 参数传引用;
void argumentObjectPassByRef(const A &a)
{
}
int main()
{
{
// 测试传值;
cout << "====== test function argument object passed by value" << endl;
A a;
argumentObjectPassByValue(a);
}
cout << endl;
{
// 测试传引用;
cout << "====== test function argument object passed by reference" << endl;
A a;
argumentObjectPassByRef(a);
}
}
测试结果
经过测试,Visual Studio 2017 Debug模式,Visual Studio 2017 Release 模式,g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容
====== test function argument object passed by value
construct
copy construct
destruct
destruct
====== test function argument object passed by reference
construct
destruct
总结
可以看到无论何种情况,都应该使用引用的方式来传入对象值,避免进行不必要的拷贝对象的消耗。至于是否需要加const作为引用的修饰,建议是如果该对象需要存储函数的操作结果的,就不加const,其他情况都使用const引用来传参数。
延伸
std::bind与std::function是C++11提供的一个高级功能,使用这两个对象可以写出非常简单的函数回调操作,那它内部是如何处理对象作为参数这一操作的呢?
1. 首先测试最简单的函数调用代码
“`c++
int main()
{
{
// 测试使用bind对象,并直接传值;
cout << “====== test use bind object, passed by value” << endl;
A a;
auto f = std::bind(argumentObjectPassedByValue, std::placeholders::_1);
f(a);
}
cout << endl;
{
// 测试使用bind对象,并直接传引用;
cout << "====== test use bind object, passed by reference" << endl;
A a;
auto f = std::bind(argumentObjectPassedByRef, std::placeholders::_1);
f(a);
}
}
```
经过测试,Visual Studio 2017 Debug模式,Visual Studio 2017 Release 模式,g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容:
```
====== test use bind object, passed by value
construct
copy construct
destruct
destruct
====== test use bind object, passed by reference
construct
destruct
```
测试结果跟直接使用原生函数一样。**传引用的方式会优于传值。**
使用function对象存储bind对象的内容,使用的还是最简单的函数调用代码
int main() { { // 测试使用function存储bind对象,并直接传值; cout << "====== test use function hold bind object, passed by value" << endl; A a; function<void(A)> f = std::bind(argumentObjectPassedByValue, std::placeholders::_1); f(a); } cout << endl; { // 测试使用function存储bind对象,并直接传引用; cout << "====== test use function hold bind object, passed by reference" << endl; A a; function<void(A)> f = std::bind(argumentObjectPassedByRef, std::placeholders::_1); f(a); } }
经过测试,Visual Studio 2017 Debug模式,Visual Studio 2017 Release 模式输出如下内容:
====== test use function hold bind object, passed by value construct copy construct rvalue copy construct destruct destruct destruct ====== test use function hold bind object, passed by reference construct destruct
g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容:
====== test use function hold bind object, passed by value construct copy construct rvalue copy construct rvalue copy construct destruct destruct destruct destruct ====== test use function hold bind object, passed by reference construct destruct
测试结果:
- 传值的结果,VS下会有一次move开销,g++下会有两次move开销
- 传引用的结果与调用原生函数一样
至于为什么会出现这种情况,还需要后续继续研究,但结果可以看出传引用的方式会优于传值。
使用bind存储函数的参数,测试代码:
int main() { { // 测试使用bind对象存储参数值,并直接传值; cout << "====== test use bind object hold argument, passed by value" << endl; A a; auto f = std::bind(argumentObjectPassedByValue, a); f(); } cout << endl; { // 测试使用bind对象存储参数值,并使用std::ref传值,; cout << "====== test use bind object hold argument, passed by value use std::ref" << endl; A a; auto f = std::bind(argumentObjectPassedByValue, std::ref(a)); f(); } cout << endl; { // 测试使用bind对象存储参数值,并直接传引用; cout << "====== test use bind object hold argument, passed by ref" << endl; A a; auto f = std::bind(argumentObjectPassedByRef, a); f(); } cout << endl; { // 测试使用bind对象存储参数值,并使用std::ref传引用,; cout << "====== test use bind object hold argument, passed by ref by std::Ref" << endl; A a; auto f = std::bind(argumentObjectPassedByRef, std::ref(a)); f(); } }
经过测试,Visual Studio 2017 Debug模式,Visual Studio 2017 Release 模式,g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容:
====== test use bind object hold argument, passed by value construct copy construct copy construct destruct destruct destruct ====== test use bind object hold argument, passed by value use std::ref construct copy construct destruct destruct ====== test use bind object hold argument, passed by ref construct copy construct destruct destruct ====== test use bind object hold argument, passed by ref by std::Ref construct destruct
测试结果分析:bind内部会存储一份函数参数的拷贝,但是当传递的是std::ref对象时,就可以避免这次拷贝,但必须确保在调用这个回调前,这个参数对象的原副本还存在,不然会导致行为未定义,出现宕机。但不论是否使用std::ref,传引用的方式都会优于传值。
使用function对象存储bind对象的内容,并存储函数参数
int main() { { // 测试使用function存储bind对象,并且bind对象存储参数值,并直接传值; cout << "====== test use function hold bind object hold argument, passed by value" << endl; A a; function<void()> f = std::bind(argumentObjectPassedByValue, a); f(); } cout << endl; { // 测试使用function存储bind对象,并且bind对象存储参数值,并使用std::ref传值,; cout << "====== test use function hold bind object hold argument, passed by value use std::ref" << endl; A a; function<void()> f = std::bind(argumentObjectPassedByValue, std::ref(a)); f(); } cout << endl; { // 测试使用function存储bind对象,并且bind对象存储参数值,并直接传引用; cout << "====== test use function hold bind object hold argument, passed by ref" << endl; A a; function<void()> f = std::bind(argumentObjectPassedByRef, a); f(); } cout << endl; { // 测试使用function存储bind对象,并且bind对象存储参数值,并使用std::ref传引用,; cout << "====== test use function hold bind object hold argument, passed by ref by std::Ref" << endl; A a; function<void()> f = std::bind(argumentObjectPassedByRef, std::ref(a)); f(); } }
经过测试,Visual Studio 2017 Debug模式,Visual Studio 2017 Release 模式,g++ 4.8 -g -O0模式,g++ 4.8 -O3模式都输出如下内容:
====== test use function hold bind object hold argument, passed by value construct copy construct rvalue copy construct destruct copy construct destruct destruct destruct ====== test use function hold bind object hold argument, passed by value use std::ref construct copy construct destruct destruct ====== test use function hold bind object hold argument, passed by ref construct copy construct rvalue copy construct destruct destruct destruct ====== test use function hold bind object hold argument, passed by ref by std::Ref construct destruct
测试结果分析:这种方式在未使用std::ref的时候,会比第三种测试方案多一次rvalue的构造,以及一次destruct操作,则是因为function和bind内部会存储一份函数参数的拷贝,当bind赋值给function对象的时候,bind内部的参数对象使用了一次move操作转移给function对象,并释放bind内部的函数参数。其他的行为和第三种测试方案一致,所以与第三种方案的结论一样。
总结
* 1. 当函数参数为对象的时候,都应该使用引用的方式来传入。*
* 2. 是否使用const,取决于该参数是否需要作为函数结果返回。*
* 3. 使用std::bind和std::function来生成回调函数的对象的时候,如果需要bind或者function对象存储参数,则优先考虑使用std::ref来传递,因为这种方式可以减少不必要的拷贝*
完整测试代码
#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
A()
{
cout << "construct" << endl;
}
A(const A& a)
{
member = a.member;
cout << "copy construct" << endl;
}
A(A&& a)
{
member = std::move(a.member);
cout << "rvalue copy construct" << endl;
}
~A()
{
cout << "destruct" << endl;
}
public:
string member;
};
// 参数传值;
void argumentObjectPassedByValue(A a)
{
}
// 参数传引用;
void argumentObjectPassedByRef(const A &a)
{
}
int main()
{
{
// 测试传值;
cout << "====== test function argument object passed by value" << endl;
A a;
argumentObjectPassedByValue(a);
}
cout << endl;
{
// 测试传引用;
cout << "====== test function argument object passed by reference" << endl;
A a;
argumentObjectPassedByRef(a);
}
cout << endl;
//////////////////////////////////////////////////////////////////////////
//
{
// 测试使用bind对象,并直接传值;
cout << "====== test use bind object, passed by value" << endl;
A a;
auto f = std::bind(argumentObjectPassedByValue, std::placeholders::_1);
f(a);
}
cout << endl;
{
// 测试使用bind对象,并直接传引用;
cout << "====== test use bind object, passed by reference" << endl;
A a;
auto f = std::bind(argumentObjectPassedByRef, std::placeholders::_1);
f(a);
}
cout << endl;
//////////////////////////////////////////////////////////////////////////
//
{
// 测试使用function存储bind对象,并直接传值;
cout << "====== test use function hold bind object, passed by value" << endl;
A a;
function<void(A)> f = argumentObjectPassedByValue; // std::bind(argumentObjectPassedByValue, std::placeholders::_1);
f(a);
}
cout << endl;
{
// 测试使用function存储bind对象,并直接传引用;
cout << "====== test use function hold bind object, passed by reference" << endl;
A a;
function<void(const A&)> f = std::bind(argumentObjectPassedByRef, std::placeholders::_1);;
f(a);
}
cout << endl;
//////////////////////////////////////////////////////////////////////////
//
{
// 测试使用bind对象存储参数值,并直接传值;
cout << "====== test use bind object hold argument, passed by value" << endl;
A a;
auto f = std::bind(argumentObjectPassedByValue, a);
f();
}
cout << endl;
{
// 测试使用bind对象存储参数值,并使用std::ref传值,;
cout << "====== test use bind object hold argument, passed by value use std::ref" << endl;
A a;
auto f = std::bind(argumentObjectPassedByValue, std::ref(a));
f();
}
cout << endl;
{
// 测试使用bind对象存储参数值,并直接传引用;
cout << "====== test use bind object hold argument, passed by ref" << endl;
A a;
auto f = std::bind(argumentObjectPassedByRef, a);
f();
}
cout << endl;
{
// 测试使用bind对象存储参数值,并使用std::ref传引用,;
cout << "====== test use bind object hold argument, passed by ref by std::Ref" << endl;
A a;
auto f = std::bind(argumentObjectPassedByRef, std::ref(a));
f();
}
cout << endl;
//////////////////////////////////////////////////////////////////////////
//
{
// 测试使用function存储bind对象,并且bind对象存储参数值,并直接传值;
cout << "====== test use function hold bind object hold argument, passed by value" << endl;
A a;
function<void()> f = std::bind(argumentObjectPassedByValue, a);
f();
}
cout << endl;
{
// 测试使用function存储bind对象,并且bind对象存储参数值,并使用std::ref传值,;
cout << "====== test use function hold bind object hold argument, passed by value use std::ref" << endl;
A a;
function<void()> f = std::bind(argumentObjectPassedByValue, std::ref(a));
f();
}
cout << endl;
{
// 测试使用function存储bind对象,并且bind对象存储参数值,并直接传引用;
cout << "====== test use function hold bind object hold argument, passed by ref" << endl;
A a;
function<void()> f = std::bind(argumentObjectPassedByRef, a);
f();
}
cout << endl;
{
// 测试使用function存储bind对象,并且bind对象存储参数值,并使用std::ref传引用,;
cout << "====== test use function hold bind object hold argument, passed by ref by std::Ref" << endl;
A a;
function<void()> f = std::bind(argumentObjectPassedByRef, std::ref(a));
f();
}
while (1);
}