鲲鹏社区首页
中文
注册
开发者
我要评分
获取效率
正确性
完整性
易理解
在线提单
论坛求助

函数传递结构或对象类型的参数时,优先采用引用传参的方式代替指针传参的方式

【说明】 C++语言中函数参数以传址的方式传递,除了支持指针传参外,相对于C语言而言,还支持引用传参的方式;相对于指针传参而言,引用传参可以传参执行效率相同的前提下,避免指针传参可能伴随的空指针检查,即在不降低安全性的前提下,减少可能的冗余指令数,因此优先采用引用传参的方式。

实际编码过程中,往往存在对数据的多层传址方式(即指针传参)使用,这种场景下可以考虑,对指针进行合法性判断后,将指针变量解引用,然后在内部进行引用传参。

【原理】 从C++编译器的底层实现分析,采用引用方式传参,与指针传参一样,均采用指针实现的方式免对象拷贝。如下,以结构对象成员操作函数为例:
struct CompValue {
    int32_t x0;
    int32_t x1;
    int32_t x2;
};
int32_t DemoFuncByValue(CompValue input1, CompValue input2, CompValue &output)
{
    output.x0 = input1.x0 + input2.x0;
    output.x1 = input1.x1 + input2.x1;
    output.x2 = input1.x2 + input2.x2;
    return 0;
}
int32_t DemoFuncByRef(CompValue &input1, CompValue &input2, CompValue &output)
{
    output.x0 = input1.x0 + input2.x0;
    output.x1 = input1.x1 + input2.x1;
    output.x2 = input1.x2 + input2.x2;
    return 0;
}
int32_t DemoFuncByPointer(CompValue *input1, CompValue *input2, CompValue &output)
{
    output.x0 = input1->x0 + input2->x0;
    output.x1 = input1->x1 + input2->x1;
    output.x2 = input1->x2 + input2->x2;
    return 0;
}
int main(){
    CompValue a{1,2,3};
    CompValue b{4,5,6};
    CompValue m{0,0,0};

    DemoFuncByValue(a, b, m);
    DemoFuncByRef(a, b, m);
    DemoFuncByPointer(&a, &b, m);
    return 0;
}
分析上述三个函数DemoFuncByValue、DemoFuncByRef、DemoFuncByPointer的调用过程,能够看到其反汇编代码如下所示,DemoFuncByValue(值传递)的调用开销最大,而DemoFuncByRef(引用传参)与DemoFuncByPointer(指针传参)的调用开销相同。
DemoFuncByValue(CompValue, CompValue, CompValue&):
        sub     sp, sp, #8
        str     lr, [sp, #-4]!
        sub     sp, sp, #20
        add     ip, sp, #16
        stmdb   ip, {r0, r1, r2}
        ldr     r2, [sp, #32]
        ldr     r0, [sp, #8]
        ldr     r1, [sp, #12]
        ldr     lr, [sp, #36]
        add     r0, r0, r2
        ldr     r2, [sp, #40]
        add     r1, r1, lr
        stmib   r2, {r0, r1}
        mov     r0, #0
        ldr     ip, [sp, #4]
        str     r3, [sp, #28]
        add     ip, ip, r3
        str     ip, [r2]
        add     sp, sp, #20
        ldr     lr, [sp], #4
        add     sp, sp, #8
        bx      lr
DemoFuncByRef(CompValue&, CompValue&, CompValue&):
        ldr     ip, [r0, #4]
        ldr     r3, [r0, #8]
        push    {r4, lr}
        ldr     r4, [r1]
        ldr     lr, [r0]
        add     r0, lr, r4
        ldmib   r1, {r4, lr}
        add     r1, ip, r4
        add     r3, r3, lr
        stm     r2, {r0, r1, r3}
        mov     r0, #0
        pop     {r4, pc}
DemoFuncByPointer(CompValue*, CompValue*, CompValue&):
        ldr     ip, [r0, #4]
        ldr     r3, [r0, #8]
        push    {r4, lr}
        ldr     r4, [r1]
        ldr     lr, [r0]
        add     r0, lr, r4
        ldmib   r1, {r4, lr}
        add     r1, ip, r4
        add     r3, r3, lr
        stm     r2, {r0, r1, r3}
        mov     r0, #0
        pop     {r4, pc}
main:
        mov     r0, #0
        bx      lr

【注意事项】 不涉及

【案例】

优化前
int32_t MsgDataProc(ModuleMsgHeader *msg)
{
    if (msg == nullptr) {
        // error handling!
    }

    // main process
    ...
    return 0;
}

说明:上述模块的消息分发接口MsgDataProc是一个通用函数,采用指针传参的方式,函数体需要对msg进行空指针保护,由于消息结构大概率是非空的,所以大部分场景下该空指针检查不会报错,但是考虑到代码的安全性,空指针检查不能删除。

优化后
int32_t MsgDataProc(ModuleMsgHeader &msg)
{
    // main process
    ...
    return 0;
}

说明:结合上下文修改,将该函数的修改为引用传参的方式,避免了空指针检查,降低执行的指令数。