C++对象在作为函数参数时候的拷贝研究

介绍

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
```
测试结果跟直接使用原生函数一样。**传引用的方式会优于传值。**
  1. 使用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开销
    • 传引用的结果与调用原生函数一样
      至于为什么会出现这种情况,还需要后续继续研究,但结果可以看出传引用的方式会优于传值。
  2. 使用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,传引用的方式都会优于传值。

  3. 使用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);
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值