您现在的位置是:首页 >技术杂谈 >现代C++技术研究(6)---编译器自动生成移动构造函数的条件网站首页技术杂谈
现代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方式定义默认的移动构造函数和移动赋值运算符,编译器就会自动生成默认的移动构造函数和移动赋值运算符,就可以解决这个问题了。