
文章目录
前言
在C++11中,扩充了许多“新的类功能”,其中涵盖了 默认移动构造/移动赋值、成员变量缺省值、default/delete 关键字、final/override 四大核心特性,他们既解决了传统类中深拷贝效率低和默认函数控制不便的缺点,又规范了继承多态中的语法细节,是现代C++类设计的基础。
1.默认的移动构造和移动赋值
1.1.核心概念与设计初衷
在C++11以前的类中,有6个默认成员函数(不写会自动生成的函数):构造函数/析构函数/拷贝构造函数/拷贝赋值函数/取地址重载/const取地址重载,其中最重要的是前4个。
C++11中新增了两个默认成员函数:移动构造函数和移动赋值运算符重载。
- 移动构造/移动赋值:接收右值引用参数的特殊成员函数,目的是“窃取”右值资源,避免拷贝。
- 设计初衷:优化深拷贝类(如string、vector等)的传值返回和容器插入等场景,减少内存拷贝与释放的开销。
1.2.编译器自动生成规则
- 如果我们没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造函数。
默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现了移动构造,如果实现了就调用移动构造,没有就调用拷贝构造。
- 如果我们没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值重载函数。
默认生成的移动赋值重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现了移动赋值,如果实现了就调用移动赋值,没有就调用拷贝赋值。
- 如果自己实现了移动构造或移动赋值重载,则编译器就不会自动提供拷贝构造和拷贝赋值。
1.3.手动实现与使用示例
代码示例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstring>
#include <utility> // 用于 std::move
using namespace std;
// 场景1:编译器自动生成默认移动构造和移动赋值
// 条件:未手动实现移动构造/移动赋值,且未实现析构、拷贝构造、拷贝赋值
class AutoGenMove {
public:
// 仅实现普通构造(无析构、拷贝构造、拷贝赋值)
AutoGenMove(const char* str = "")
: _size(strlen(str)) {
_str = new char[_size + 1];
strcpy(_str, str);
cout << "AutoGenMove 普通构造" << endl;
}
// 成员变量(内置类型+自定义类型,验证不同成员的移动行为)
char* _str;
size_t _size;
string _s; // string已实现移动构造/赋值,用于验证自定义类型成员行为
};
// 场景2:手动实现移动构造/移动赋值,编译器禁止自动生成拷贝构造/赋值
class ManualMove {
public:
ManualMove(const char* str = "")
: _size(strlen(str)) {
_str = new char[_size + 1];
strcpy(_str, str);
cout << "ManualMove 普通构造" << endl;
}
// 手动实现移动构造(窃取右值资源)
ManualMove(ManualMove&& other)
: _str(other._str)
, _size(other._size)
, _s(move(other._s)) { // 自定义类型成员调用其移动构造
other._str = nullptr; // 置空源对象,避免析构重复释放
other._size = 0;
cout << "ManualMove 移动构造" << endl;
}
// 手动实现移动赋值(窃取右值资源)
ManualMove& operator=(ManualMove&& other) {
if (this != &other) {
// 释放当前对象资源
delete[] _str;
// 窃取源对象资源
_str = other._str;
_size = other._size;
_s = move(other._s); // 自定义类型成员调用其移动赋值
// 置空源对象
other._str = nullptr;
other._size = 0;
}
cout << "ManualMove 移动赋值" << endl;
return *this;
}
// 析构函数(释放动态内存)
~ManualMove() {
delete[] _str;
cout << "ManualMove 析构" << endl;
}
private:
char* _str;
size_t _size;
string _s;
};
// 场景3:实现析构函数,编译器不自动生成移动构造/赋值
class NoAutoMove {
public:
NoAutoMove(const char* str = "")
: _size(strlen(str)) {
_str = new char[_size + 1];
strcpy(_str, str);
cout << "NoAutoMove 普通构造" << endl;
}
// 实现析构函数(触发规则:编译器不再自动生成移动构造/赋值)
~NoAutoMove() {
delete[] _str;
cout << "NoAutoMove 析构" << endl;
}
private:
char* _str;
size_t _size;
};
int main() {
// 测试场景1:编译器自动生成移动构造/赋值
cout << "=== 场景1:自动生成移动构造/赋值 ===" << endl;
AutoGenMove a1("auto1");
AutoGenMove a2 = move(a1); // 调用编译器生成的移动构造(a1._str被窃取)
AutoGenMove a3;
a3 = move(a2); // 调用编译器生成的移动赋值(a2._str被窃取)
// 验证:a1._str已被置空(编译器生成的移动构造会逐字节拷贝指针,需手动置空?不,内置类型仅拷贝值,源对象指针仍指向原内存!)
// 注意:编译器生成的移动构造对内置指针仅拷贝地址,可能导致double free,需手动实现移动构造处理!
cout << "a1._str: " << (a1._str ? a1._str : "nullptr") << endl; // 此处a1._str仍指向原内存,存在风险(需手动实现移动构造优化)
// 测试场景2:手动实现移动构造/赋值,禁止自动生成拷贝构造/赋值
cout << "\n=== 场景2:手动实现移动,禁止拷贝 ===" << endl;
ManualMove m1("manual1");
ManualMove m2 = move(m1); // 调用手动实现的移动构造(m1._str被置空)
ManualMove m3;
m3 = move(m2); // 调用手动实现的移动赋值(m2._str被置空)
// ManualMove m4 = m3; // 编译报错:编译器未自动生成拷贝构造(手动实现移动后禁止)
// m3 = m2; // 编译报错:编译器未自动生成拷贝赋值(手动实现移动后禁止)
// 测试场景3:实现析构,不自动生成移动构造/赋值
cout << "\n=== 场景3:实现析构,无自动移动 ===" << endl;
NoAutoMove n1("noauto1");
// NoAutoMove n2 = move(n1); // 编译报错:编译器未自动生成移动构造(实现析构触发规则)
// NoAutoMove n3;
// n3 = move(n1); // 编译报错:编译器未自动生成移动赋值(实现析构触发规则)
return 0;
}
测试结果:

2.成员变量声明时给缺省值
2.1.语法规则与作用机制
- 声明语法:类内成员变量声明时,直接赋值,仅为初始化提供缺省值
class A{
//...
private:
int _a = 1;
//...
};
- 生效场景:未在构造函数初始化列表显式初始化时,使用该缺省值初始化成员变量
2.2.优势与使用场景
- 简化代码:减少构造函数重载
- 统一默认值:集中管理成员变量默认值,便于维护
- 代码示例:
传统缺省值方式:
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string _name;
int _age;
string _gender;
// 全参数构造
Person(string name, int age, string gender)
: _name(name), _age(age), _gender(gender) {}
// 缺省gender的构造(复用全参构造)
Person(string name, int age)
: Person(name, age, "未知") {}
// 缺省age和gender的构造(复用上面的构造)
Person(string name)
: Person(name, 0, "未知") {}
// 无参构造(复用上面的构造)
Person()
: Person("", 0, "未知") {}
};
int main() {
Person p1; // 无参:name="", age=0, gender="未知"
Person p2("张三"); // 仅name:age=0, gender="未知"
Person p3("李四", 20); // name+age:gender="未知"
Person p4("王五", 25, "男"); // 全参数
return 0;
}
C++11缺省值方式:
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
// 成员变量直接声明缺省值(集中管理默认值)
string _name = "";
int _age = 0;
string _gender = "未知";
// 仅需一个构造函数,参数带默认值即可
Person(string name = "", int age = 0, string gender = "未知")
: _name(name), _age(age), _gender(gender) {}
};
int main() {
Person p1; // 无参:使用所有缺省值
Person p2("张三"); // 仅传name:其他用缺省值
Person p3("李四", 20); // 传name+age:gender用缺省值
Person p4("王五", 25, "男"); // 全参数:覆盖缺省值
return 0;
}
核心差异对比:
| 特点 | 传统构造函数重载 | 成员缺省值 |
|---|---|---|
| 代码量 | 需要多个重载函数,冗余 | 仅需一个构造函数,简洁 |
| 默认值维护 | 分散在多个构造函数,容易出错 | 集中在成员声明处,便于修改 |
| 扩展性 | 新增成员需修改所有相关构造函数 | 新增成员仅需添加缺省值和参数 |
2.3.与初始化列表的优先级
- 构造函数初始化列表 > 构造函数体内赋值 > 构造函数参数默认值 > 成员变量声明时的缺省值。
注意:const成员、引用成员需通过初始化列表显式初始化,不可以用声明时缺省值。
3.default和delete关键字
- C++11可以让我们更好的控制要使用的默认函数。假设我们要使用某个默认函数,但它没有默认生成。比如:我们提供了拷贝构造,那么移动构造就不会自动生成了,此时我们可以用default关键字显式指定移动构造生成。
- 在C++98中,如果想要限制某个默认函数的生成,那么可以将这个函数设置成private,并且只声明不定义,这样就禁止了外部的直接调用;在C++11中,只需在该函数声明加上
=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数
代码示例:
#include <iostream>
#include <utility>
using namespace std;
// 示例1:C++11中用default显式生成默认函数
class UseDefault {
public:
// 手动实现拷贝构造(此时编译器不会自动生成移动构造/赋值)
UseDefault(const UseDefault& other)
: _data(other._data) {
cout << "UseDefault 拷贝构造" << endl;
}
// 用default显式要求编译器生成默认移动构造
UseDefault(UseDefault&&) = default;
// 用default显式要求编译器生成默认移动赋值
UseDefault& operator=(UseDefault&&) = default;
// 普通构造与析构
UseDefault(int data = 0) : _data(data) {}
~UseDefault() = default; // 显式生成默认析构
int _data;
};
// 示例2:C++98禁止默认函数的方式(私有化+只声明不定义)
class Cpp98NoCopy {
private:
// 只声明不定义,且设为私有
Cpp98NoCopy(const Cpp98NoCopy&);
Cpp98NoCopy& operator=(const Cpp98NoCopy&);
public:
Cpp98NoCopy() { cout << "Cpp98NoCopy 构造" << endl; }
};
// 示例3:C++11用delete禁止默认函数的方式
class Cpp11NoCopy {
public:
Cpp11NoCopy() { cout << "Cpp11NoCopy 构造" << endl; }
// 用delete明确禁止生成拷贝构造和拷贝赋值
Cpp11NoCopy(const Cpp11NoCopy&) = delete;
Cpp11NoCopy& operator=(const Cpp11NoCopy&) = delete;
// delete也可用于禁止普通函数的特定重载
void func(int) {}
void func(double) = delete; // 禁止double参数的调用
};
int main() {
// 测试default:手动实现拷贝构造后,仍可通过default获得移动构造
cout << "=== 测试default ===" << endl;
UseDefault u1(10);
UseDefault u2 = move(u1); // 调用编译器生成的默认移动构造
UseDefault u3;
u3 = move(u2); // 调用编译器生成的默认移动赋值
// 测试C++98禁止拷贝
cout << "\n=== 测试C++98禁止拷贝 ===" << endl;
Cpp98NoCopy c98_1;
// Cpp98NoCopy c98_2(c98_1); // 编译报错:无法访问私有拷贝构造
// 测试C++11禁止拷贝
cout << "\n=== 测试C++11禁止拷贝 ===" << endl;
Cpp11NoCopy c11_1;
// Cpp11NoCopy c11_2(c11_1); // 编译报错:使用了已删除的函数
// c11_2 = c11_1; // 编译报错:使用了已删除的函数
// 测试delete禁止普通函数重载
Cpp11NoCopy c11;
c11.func(10); // 正常调用int版本
// c11.func(3.14); // 编译报错:调用了已删除的函数
return 0;
}
测试结果:

4.final与override关键字
4.1.final:限制继承与重写
- 修饰类:该类不可被继承
// 用final修饰Base类,使其不可被继承
class Base final {
public:
void show() {
cout << "这是不可继承的Base类" << endl;
}
};
// 尝试继承final修饰的类会编译报错
// class Derived : public Base { // 错误:无法从最终类“Base”派生
// public:
// void show() {
// cout << "尝试继承Base类" << endl;
// }
// };
- 修饰虚函数:该函数(Function)不可被派生类重写
class Base {
public:
// 用final修饰虚函数func,使其不可被派生类重写
virtual void func() final {
cout << "Base类中不可重写的func()" << endl;
}
// 普通虚函数,允许被重写
virtual void other() {
cout << "Base类中可重写的other()" << endl;
}
};
class Derived : public Base {
public:
// 尝试重写final修饰的虚函数会编译报错
// void func() override { // 错误:无法重写最终函数“Base::func”
// cout << "尝试重写func()" << endl;
// }
// 可以正常重写非final的虚函数
void other() override {
cout << "Derived类中重写的other()" << endl;
}
};
4.2.override:确保虚函数重写正确性
- 语法格式:派生类重写虚函数时,尾部添加override关键字即可
- 核心作用:编译时检查重写规则(函数名、参数列表、返回值类型需与基类完全一致),避免隐式错误。
- 代码示例:
#include <iostream>
using namespace std;
class Base {
public:
// 基类虚函数:参数为int,无返回值
virtual void print(int num) {
cout << "Base::print(int): " << num << endl;
}
// 基类虚函数:无参数
virtual void display() {
cout << "Base::display()" << endl;
}
};
class Derived : public Base {
public:
// 正确示例:完全匹配基类虚函数(参数、函数名一致),添加override
void print(int num) override {
cout << "Derived::print(int): " << num << endl;
}
// 错误示例1:参数类型不匹配(基类是int,此处是double)
// void print(double num) override { // 编译报错:与基类虚函数参数列表不匹配
// cout << "Derived::print(double): " << num << endl;
// }
// 错误示例2:函数名拼写错误(基类是display,此处是disply)
// void disply() override { // 编译报错:基类中无“disply”虚函数
// cout << "Derived::disply()" << endl;
// }
// 错误示例3:重写非虚函数(若基类display不是virtual)
// 注:此处基类display是virtual,若去掉virtual,下面代码会报错
// void display() override { // 编译报错:无法重写非虚函数
// cout << "Derived::display()" << endl;
// }
};
int main() {
Base* ptr = new Derived();
ptr->print(100); // 调用Derived的print(正确重写)
ptr->display(); // 调用Base的display(未被重写)
delete ptr;
return 0;
}
5.注意事项
5.1.移动语义
- 不是所有的类都需要手动实现移动构造/赋值(浅拷贝类不需要,编译器生成的足矣)
5.2.default/delete
- delete修饰的函数禁止所有调用(
包括友元) - 析构函数不可用delete,否则对象将无法析构
5.3.成员变量缺省值
- 类内缺省值不是初始化,实际初始化在构造函数执行时
- 静态成员变量的初始值需在类外定义
5.4.final/override
- override仅能用于虚函数的重写,不能修饰非虚函数
- final修饰的类可以初始化,仅用于禁止继承
总结
本文详细探讨了 C++11 标准为类机制带来的四项核心增强:默认移动语义、成员变量缺省值、default/delete 关键字以及 final/override 关键字。这些特性共同构成了现代C++类设计的基础,解决了传统C++存在的效率、代码冗余以及类型安全等问题。掌握以上内容,能帮助我们编写更高效、更简洁、更安全且更易于维护的现代C++代码。
1646

被折叠的 条评论
为什么被折叠?



