您现在的位置是:首页 >技术杂谈 >现代C++技术研究(6)---编译器自动生成移动构造函数的条件网站首页技术杂谈

现代C++技术研究(6)---编译器自动生成移动构造函数的条件

swordmanwk 2024-06-17 11:19:12
简介现代C++技术研究(6)---编译器自动生成移动构造函数的条件

C++11以后,支持移动构造函数和移动赋值运算符,这样语言特性明显提高了我们编写的程序的性能。一般来说,编译器会自动生成的移动构造函数,足够满足我们的使用需求,但是有些场景编译器不会自动生成移动构造函数,这会影响我们编写代码的性能。因此必须要识别出哪些场景编译器可以自动生成移动构造函数哪些场景不能。对于不能的场景,我们需要手工编写移动构造函数和移动赋值运算符。

场景1,如果我们在一个类里不手工编写任何构造函数,析构函数,拷贝赋值运算符和移动赋值运算符,编译器在需要的时候会给我们生成移动构造函数。例如:

#include <vector>

class A {
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看生成的汇编代码(ARM64 gcc13.1.0 --std=c++14),可以看到:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::A(A&&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        ldr     x0, [sp, 24]
        ldr     x1, [sp, 16]
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> >&&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

编译器自动生成了移动构造函数,该函数调用了vector<int>的移动构造函数。

场景2:我们编写默认构造函数:

#include <vector>

class A {
public:
    A() noexcept
    {
        vi_.resize(10);
    }
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看汇编代码:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        ldr     x0, [sp, 24]
        mov     x1, 10
        bl      std::vector<int, std::allocator<int> >::resize(unsigned long)
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::A(A&&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        ldr     x0, [sp, 24]
        ldr     x1, [sp, 16]
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> >&&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

可以看到,移动构造函数仍然自动生成,所以,默认构造函数的定制,是没有影响的。

场景3:显示的声明析构函数:

#include <vector>

class A {
public:
    ~A() = default;
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看汇编结果:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::A(A const&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        ldr     x0, [sp, 24]
        ldr     x1, [sp, 16]
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

可以看到,编译器自动生成的是拷贝构造函数,而不是移动构造函数了。

场景4:显示定义拷贝构造函数:

#include <vector>

class A {
public:
    A() = default;
    A(const A& a)
    {
        if (this == &a) {
            return;
        }
        vi_ = a.vi_;
    }
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看汇编代码:

A::A(A const&) [base object constructor]:
        stp     x29, x30, [sp, -48]!
        mov     x29, sp
        str     x19, [sp, 16]
        str     x0, [sp, 40]
        str     x1, [sp, 32]
        ldr     x0, [sp, 40]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        ldr     x1, [sp, 40]
        ldr     x0, [sp, 32]
        cmp     x1, x0
        beq     .L9
        ldr     x0, [sp, 40]
        ldr     x1, [sp, 32]
        bl      std::vector<int, std::allocator<int> >::operator=(std::vector<int, std::allocator<int> > const&)
        b       .L4
        mov     x19, x0
        ldr     x0, [sp, 40]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        mov     x0, x19
        bl      _Unwind_Resume
.L9:
        nop
.L4:
        ldr     x19, [sp, 16]
        ldp     x29, x30, [sp], 48
        ret
A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

可以看到,编译器没有自动生成移动构造函数。

场景5:显示定义拷贝赋值运算符:

#include <vector>

class A {
public:
    A() = default;
    A& operator = (const A& a)
    {
        if (this != &a) {
            vi_ = a.vi_;
        }
        return *this;
    }
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看汇编代码:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::A(A const&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        ldr     x0, [sp, 24]
        ldr     x1, [sp, 16]
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

可以看到,编译器实际生成的是拷贝构造函数,没有生成移动构造函数。

场景6:显示定义移动构造函数,这种场景当然编译器不会自动生成移动构造函数,代码略。

场景7:显示定义移动赋值运算符,但是不定义移动构造函数场景,这种场景编译器也不会自动生成移动构造函数,而且还不会自动生成拷贝构造函数,下面的代码就编译不过了:

#include <vector>

class A {
public:
    A() = default;
    A& operator = (A&& a)
    {
        if (this != &a) {
            vi_ = std::move(a.vi_);
        }
        return *this;
    }
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

场景8:定义其他类型的构造函数,比如:

#include <vector>

class A {
public:
    A() = default;
    explicit A(int length) : vi_(length) {}
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

查看汇编代码:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::A(A&&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        ldr     x0, [sp, 24]
        ldr     x1, [sp, 16]
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> >&&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret

可以看到,编译器自动生成了移动构造函数,也就是说,没有影响。

那么如果编译器没有自动生成移动构造函数,我们怎么解决这个问题,需要手工写一个吗?一般来说,是没有必要的,只要写一个默认的就可以了,比如:

#include <vector>

class A {
public:
    A() = default;
    virtual ~A() = default;
    A(A && a) = default;
    A& operator = ( A && a) = default;
private:
    std::vector<int> vi_;
};

int main()
{
    A a;
    A b = std::move(a);
}

这样,后面程序演进的时候,新增成员变量,不需要同步修改移动构造函数和移动赋值运算符,生成的完整汇编如下:

A::A() [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        adrp    x0, vtable for A+16
        add     x1, x0, :lo12:vtable for A+16
        ldr     x0, [sp, 24]
        str     x1, [x0]
        ldr     x0, [sp, 24]
        add     x0, x0, 8
        bl      std::vector<int, std::allocator<int> >::vector() [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [base object destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        adrp    x0, vtable for A+16
        add     x1, x0, :lo12:vtable for A+16
        ldr     x0, [sp, 24]
        str     x1, [x0]
        ldr     x0, [sp, 24]
        add     x0, x0, 8
        bl      std::vector<int, std::allocator<int> >::~vector() [complete object destructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
A::~A() [deleting destructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        ldr     x0, [sp, 24]
        bl      A::~A() [complete object destructor]
        mov     x1, 32
        ldr     x0, [sp, 24]
        bl      operator delete(void*, unsigned long)
        ldp     x29, x30, [sp], 32
        ret
A::A(A&&) [base object constructor]:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     x0, [sp, 24]
        str     x1, [sp, 16]
        adrp    x0, vtable for A+16
        add     x1, x0, :lo12:vtable for A+16
        ldr     x0, [sp, 24]
        str     x1, [x0]
        ldr     x0, [sp, 24]
        add     x2, x0, 8
        ldr     x0, [sp, 16]
        add     x0, x0, 8
        mov     x1, x0
        mov     x0, x2
        bl      std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> >&&) [complete object constructor]
        nop
        ldp     x29, x30, [sp], 32
        ret
main:
        stp     x29, x30, [sp, -80]!
        mov     x29, sp
        add     x0, sp, 48
        bl      A::A() [complete object constructor]
        add     x0, sp, 48
        bl      std::remove_reference<A&>::type&& std::move<A&>(A&)
        mov     x1, x0
        add     x0, sp, 16
        bl      A::A(A&&) [complete object constructor]
        add     x0, sp, 16
        bl      A::~A() [complete object destructor]
        add     x0, sp, 48
        bl      A::~A() [complete object destructor]
        mov     w0, 0
        ldp     x29, x30, [sp], 80
        ret
vtable for A:
        .xword  0
        .xword  typeinfo for A
        .xword  A::~A() [complete object destructor]
        .xword  A::~A() [deleting destructor]
typeinfo for A:
        .xword  _ZTVN10__cxxabiv117__class_type_infoE+16
        .xword  typeinfo name for A
typeinfo name for A:
        .string "1A"
DW.ref.__gxx_personality_v0:
        .xword  __gxx_personality_v0

总结一下:当用户自定义移动构造函数,或移动赋值运算符,或拷贝构造函数,或拷贝赋值运算符,或析构函数时,编译器不会自动生成移动构造函数和移动赋值运算符,这时,只要通过default方式定义默认的移动构造函数和移动赋值运算符,编译器就会自动生成默认的移动构造函数和移动赋值运算符,就可以解决这个问题了。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。