C++11 四大特性精讲
-
- [一、std::atomic 原子操作](#一、std::atomic 原子操作)
-
- [1. 为什么普通 int 加法线程不安全](#1. 为什么普通 int 加法线程不安全)
-
- [错误示例(普通 int 多线程累加)](#错误示例(普通 int 多线程累加))
- [2. std::atomic<int> 原子加法(线程安全)](#2. std::atomic<int> 原子加法(线程安全))
-
- [基础用法:原子自增 cnt++、原子加法 cnt += n](#基础用法:原子自增 cnt++、原子加法 cnt += n)
- [3. 原子操作配套接口(`load` / `store`)](#3. 原子操作配套接口(
load/store)) - [4. 关键约束](#4. 关键约束)
- 二、nullptr标准空指针
-
- [1. 历史问题:0 和 NULL](#1. 历史问题:0 和 NULL)
- [2. nullptr 核心特性](#2. nullptr 核心特性)
- [3. 重载场景对比(最经典案例)](#3. 重载场景对比(最经典案例))
- [4. 日常标准用法](#4. 日常标准用法)
- [5. 总结规范](#5. 总结规范)
- [三、=default / =delete 控制默认成员函数](#三、=default / =delete 控制默认成员函数)
-
- [1. = default:显式使用编译器默认实现](#1. = default:显式使用编译器默认实现)
- [2. = delete:删除函数(禁用调用)](#2. = delete:删除函数(禁用调用))
-
- 场景1:禁用默认构造(无法无参创建对象)
- [场景2:禁用拷贝构造 + 拷贝赋值(不可拷贝类)](#场景2:禁用拷贝构造 + 拷贝赋值(不可拷贝类))
- 场景3:禁用普通重载函数
- [四、enum class强类型枚举](#四、enum class强类型枚举)
-
- [1. 传统 enum的两大缺陷](#1. 传统 enum的两大缺陷)
- [2. enum class 强枚举语法 & 核心特性](#2. enum class 强枚举语法 & 核心特性)
-
- [特性1:独立作用域,必须 `枚举名::` 访问](#特性1:独立作用域,必须
枚举名::访问) - [特性2:禁止隐式转 int,类型严格安全](#特性2:禁止隐式转 int,类型严格安全)
- 特性3:解决命名冲突
- 特性4:自定义底层存储类型
- [特性1:独立作用域,必须 `枚举名::` 访问](#特性1:独立作用域,必须
- [3. 枚举比较规则](#3. 枚举比较规则)
- 综合速记总结
一、std::atomic 原子操作
头文件:#include <atomic>
1. 为什么普通 int 加法线程不安全
i++ / i += n 看似一行代码,底层会拆成三条CPU指令 :读内存 → 运算 → 写回内存。
多线程交替执行时,指令被打断,就会出现数据丢失。
错误示例(普通 int 多线程累加)
cpp
#include <iostream>
#include <thread>
// 普通整型,非原子
int cnt = 0;
// 线程函数:循环累加 100000 次
void add()
{
for (int i = 0; i < 100000; ++i)
{
cnt += 1; // 非原子加法,线程不安全
}
}
int main()
{
// 开启两个线程同时累加
std::thread t1(add);
std::thread t2(add);
t1.join();
t2.join();
// 预期结果:200000,实际大概率小于 200000
std::cout << "普通int累加结果:" << cnt << std::endl;
return 0;
}
原因:两个线程同时读取到相同的 cnt 值,各自+1后写回,一次加法被覆盖。
2. std::atomic 原子加法(线程安全)
std::atomic把读写、自增、加减赋值 全部封装为硬件原子指令,整个操作不可中断。
基础用法:原子自增 cnt++、原子加法 cnt += n
cpp
#include <iostream>
#include <thread>
#include <atomic>
// 原子整型
std::atomic<int> cnt{0};
void add()
{
for (int i = 0; i < 100000; ++i)
{
cnt += 1; // 原子加法:线程安全
// cnt++; // 等价,也是原子操作
}
}
int main()
{
std::thread t1(add);
std::thread t2(add);
t1.join();
t2.join();
// 结果一定是 200000
std::cout << "atomic<int> 累加结果:" << cnt << std::endl;
return 0;
}
3. 原子操作配套接口(load / store)
load():原子读,读取原子变量的值store(val):原子写,向原子变量写入值
cpp
std::atomic<int> num{10};
int a = num.load(); // 原子读取,a = 10
num.store(20); // 原子写入,num = 20
num += 5; // 原子加法,num = 25
int b = num.load(); // b = 25
4. 关键约束
-
std::atomic<T>禁止拷贝构造、拷贝赋值cppstd::atomic<int> x{0}; // std::atomic<int> y = x; // 编译报错,不能拷贝 -
仅保证单一原子变量 操作安全,多行代码组合依然有竞争
cppstd::atomic<int> a{0}, b{0}; if (a > 0) b += 1; // 整体逻辑非原子,仍存在线程安全问题 -
只支持内置类型、指针,不支持自定义类。
二、nullptr标准空指针
1. 历史问题:0 和 NULL
C++ 中 #define NULL 0,本质是整型 0,和真正的空指针类型割裂。
2. nullptr 核心特性
- C++11 关键字,专属空指针类型
std::nullptr_t - 可隐式转为任意指针,不能隐式转为整型
- 彻底解决函数重载歧义
3. 重载场景对比(最经典案例)
cpp
#include <iostream>
// 整型重载
void func(int)
{
std::cout << "匹配 int 版本" << std::endl;
}
// 指针重载
void func(void*)
{
std::cout << "匹配 指针 版本" << std::endl;
}
int main()
{
func(0); // 字面量0 → 调用 int
func(NULL); // NULL=0 → 依旧调用 int(不符合空指针意图)
func(nullptr); // 纯空指针 → 精准调用 void*(正确)
// int n = nullptr; // 编译报错:禁止转整型
return 0;
}
4. 日常标准用法
(1)指针初始化/置空
cpp
int* p = nullptr;
char* pc = nullptr;
void (*fp)() = nullptr; // 函数指针
p = nullptr; // 运行时置空
(2)空指针判断
cpp
if (p == nullptr)
{
// 指针为空,推荐写法,语义清晰
}
if (!p)
{
// 简写写法,语法合法
}
5. 总结规范
新项目一律使用 nullptr,放弃 NULL 和裸 0 表示空指针。
三、=default / =delete 控制默认成员函数
C++ 类有 6 个编译器默认生成的特殊成员函数:
默认构造、析构、拷贝构造、拷贝赋值、移动构造、移动赋值。
1. = default:显式使用编译器默认实现
作用
手动写了有参构造 后,编译器不再生成默认构造,用 =default 恢复默认构造。
示例
cpp
class Student
{
public:
// 显式让编译器生成 默认无参构造
Student() = default;
// 自定义有参构造
Student(int id)
{
std::cout << "有参构造" << std::endl;
}
};
int main()
{
Student s1; // 合法:调用 default 默认构造
Student s2(1); // 合法:调用有参构造
return 0;
}
拓展:也可用于析构、拷贝、移动函数,统一使用编译器默认逻辑。
2. = delete:删除函数(禁用调用)
调用被 delete 修饰的函数,直接编译报错,用于限制对象行为。
场景1:禁用默认构造(无法无参创建对象)
cpp
class A
{
public:
A() = delete; // 删除默认构造
A(int x) {}
};
// A a; // 编译报错:默认构造已被删除
A b(10); // 合法
场景2:禁用拷贝构造 + 拷贝赋值(不可拷贝类)
常用于单例、独占资源类,禁止对象被拷贝:
cpp
class NoCopy
{
public:
NoCopy() = default;
// 删除拷贝构造
NoCopy(const NoCopy&) = delete;
// 删除拷贝赋值
NoCopy& operator=(const NoCopy&) = delete;
};
NoCopy obj1;
// NoCopy obj2 = obj1; // 编译报错,拷贝被禁用
// obj1 = obj2; // 编译报错,赋值被禁用
场景3:禁用普通重载函数
拦截非法参数传入:
cpp
void test(int) {}
void test(double) = delete;
// test(3.14); // 编译报错:double 版本被删除
四、enum class强类型枚举
1. 传统 enum的两大缺陷
- 作用域泄露:枚举常量直接暴露在当前作用域,容易重名
- 隐式转 int:枚举和整型随意混用,类型不安全
cpp
// 传统弱枚举(不推荐)
enum Color { Red, Green, Blue };
// enum Light { Red, Yellow }; // 编译报错:Red 重名冲突
int a = Red; // 隐式转为 int,类型无检查
Color c = 1; // 整型直接赋值给枚举,非法值也能通过
2. enum class 强枚举语法 & 核心特性
语法格式:
cpp
enum class 枚举名 [: 底层类型]
{
常量1,
常量2 = 自定义值
};
特性1:独立作用域,必须 枚举名:: 访问
cpp
enum class Color
{
Red,
Green,
Blue
};
int main()
{
// Color c = Red; // 报错:Red 不在当前作用域
Color c = Color::Red; // 正确写法
return 0;
}
特性2:禁止隐式转 int,类型严格安全
cpp
enum class Color : int
{
Red = 1,
Green,
Blue
};
Color c = Color::Green;
// int val = c; // 编译报错:不允许隐式转整型
// 如需转整型,必须显式强转
int val = static_cast<int>(c);
特性3:解决命名冲突
多个枚举可以拥有同名常量:
cpp
enum class Color { Red, Green };
enum class Light { Red, Yellow };
Color c = Color::Red;
Light l = Light::Red; // 完全合法,作用域隔离
特性4:自定义底层存储类型
可指定 char/short/unsigned int 节省内存:
cpp
// 底层为 char,仅占 1 字节
enum class Status : char
{
Off = 0,
On = 1
};
3. 枚举比较规则
- 同一种强枚举之间可以正常比较
- 枚举 和
int、不同枚举之间不能直接比较
cpp
enum class Color { Red, Green };
Color c1 = Color::Red;
Color c2 = Color::Green;
if (c1 != c2) {} // 合法
// if (c1 == 0) {} // 报错:不能和整型比较
综合速记总结
std::atomic<int>
专门解决多线程下int自增/加法的数据竞争,操作原子不可中断,无需手动加锁。nullptr
标准空指针,替代NULL/0,解决重载歧义,类型安全。=default/=delete
=default:启用编译器默认成员函数;
=delete:禁用函数调用,编译期拦截非法操作。enum class
强类型枚举,作用域隔离、禁止隐式转整型,比传统enum更安全,工程开发首选。