默认的移动构造和移动赋值
在 C++11 之前,编译器会为每个类自动生成默认的构造函数、析构函数、拷贝构造函数、拷贝赋值运算符等函数,以实现对象的创建、销毁和拷贝操作。但拷贝操作会复制整个对象的数据,效率低,尤其是在处理大对象或动态分配的资源时。为了解决这一问题,C++11 引入了移动语义(Move Semantics),并提供了两个新的默认函数:移动构造函数和移动赋值运算符。
移动构造和移动赋值运算符的作用是通过“转移”资源来代替“复制”资源,提高效率。它们通常用于避免不必要的拷贝操作,尤其是当资源的原有所有者不再需要保留这些资源时。
生成规则
- 若类未定义析构函数、拷贝构造函数、拷贝赋值运算符或移动构造函数,编译器会自动生成默认的移动构造和移动赋值运算符。
- 若类包含自定义析构函数、拷贝构造函数或拷贝赋值运算符,则编译器不会自动生成移动构造和移动赋值运算符,除非显式指定
=default
。
移动构造函数和移动赋值的行为
- 内置类型成员将按字节逐一拷贝。
- 自定义类型成员会优先调用它们的移动构造函数(若存在),否则调用拷贝构造函数。
也就是说,与其他的默认函数一样,自定义类型仍然需要看本身有没有实现相应的函数。
示例代码
#include <string>
#include <utility> // 包含std::moveclass Person {
public:Person(const char* name = "", int age = 0): _name(name), _age(age) {}// 移动构造函数自动生成,因为未定义拷贝构造或赋值函数private:std::string _name;int _age;
};int main() {Person p1("Alice", 25);Person p2 = std::move(p1); // 触发移动构造return 0;
}
在上述代码中,std::move
将 p1
转换为右值引用(rvalue reference),指示编译器可以“转移”p1
的资源,而无需拷贝。p1
的数据会被移动到 p2
,此操作避免了 p1
的拷贝过程。
注意
若类定义了移动构造函数或移动赋值运算符,编译器不会再自动生成拷贝构造函数和拷贝赋值运算符。
声明时给缺省值
在 C++11 之前,默认参数值只能在函数声明中给出,不能直接在成员变量定义时赋值。而 C++11 允许在类的成员变量声明时直接赋默认值,这一特性提高了代码的简洁性,并增强了初始化的灵活性。这样,在构造对象时,若未传入对应参数,成员变量会自动采用声明时指定的默认值。
示例代码
class Person {
public:Person(const char* name = "") : _name(name) {}private:std::string _name = "DefaultName"; // 直接在声明中赋缺省值int _age = 18; // 默认值为 18
};int main() {Person p; // _name 初始化为 "DefaultName",_age 初始化为 18return 0;
}
通过在声明时赋值,减少了构造函数中初始化的代码量,避免重复设置默认值,提高了可读性。
default
和 delete
C++11 引入了 =default
和 =delete
,提供更精细的控制:
=default
:当类中定义了某个自定义函数(如拷贝构造),编译器不会自动生成移动构造函数。若希望保留自动生成的行为,可使用=default
显式要求编译器生成该函数。=delete
:通过=delete
,可以禁用类的某些默认行为(如拷贝或赋值),例如禁用拷贝构造可以避免误用拷贝构造函数带来的资源分配问题。
示例代码:
class Person {
public:
Person(const char* name = "", int age = 0)
: _name(name), _age(age) {}Person(const Person& p) = delete; // 禁用拷贝构造函数
Person(Person&& p) = default; // 显式要求生成默认移动构造函数private:
std::string _name;
int _age;
};int main() {Person s1("Alice", 25);// Person s2 = s1; // 错误:拷贝构造函数被禁用Person s3 = std::move(s1); // 调用默认的移动构造函数return 0;
}
final
和 override
在 C++ 的继承和多态中,派生类可能会误写或错写基类的虚函数,导致未按预期覆盖基类的行为。C++11 提供了 final
和 override
关键字,帮助开发者更好地控制和检测继承链中的函数覆盖行为。
override
:用于修饰派生类中的虚函数,表示这是对基类中同名虚函数的覆盖。如果函数签名不匹配,编译器会报错。final
:用于修饰类或虚函数,表示该类或虚函数不允许被进一步继承或重写。
示例代码
class Base {
public:virtual void display() const {std::cout << "Base class display" << std::endl;}virtual void show() const final { // 不允许派生类重写此函数std::cout << "Base class show" << std::endl;}
};class Derived : public Base {
public:void display() const override { // 正确覆盖基类的displaystd::cout << "Derived class display" << std::endl;}// void show() const override; // 错误:不能重写final函数
};// class FurtherDerived : public Derived final {}; // 不允许进一步派生
override
关键字帮助避免虚函数重写错误,final
关键字则能阻止类或函数的进一步派生和重写,提升代码安全性和可读性。