C++11/14 新语法速查:nullptr、constexpr、using、委托构造
维护老代码最划算的改造,往往不是大重构,而是把这些"零风险、高收益"的语法点逐步替换掉。本文按迁移顺序排列,每条都给 C++98 对照写法。
一、nullptr 替代 NULL 与 0
NULL 在多数实现里是 0 或 0L,于是重载决议会出问题:
void f(int);
void f(char*);
f(NULL); // ❌ 调用 f(int),因为 NULL 是整数常量
f(nullptr); // ✅ 调用 f(char*)
nullptr 的类型是 std::nullptr_t,可隐式转换为任意指针类型,但不能转换为整数。迁移做法:
// C++98
char* p = NULL;
if (p == NULL) { /* ... */ }
// C++11
char* p = nullptr;
if (p == nullptr) { /* ... */ }
注意:nullptr 是关键字,老代码里把它当变量名的必须改名。
二、constexpr:把计算搬到编译期
constexpr 声明的函数或变量,在参数为常量表达式时于编译期求值,否则退化为运行期,不损失性能:
// C++11:函数体只能有一条 return
constexpr int square(int x) { return x * x; }
constexpr int N = square(5); // 编译期得到 25
// C++14:函数体放宽,可以有局部变量、循环、分支
constexpr int factorial(int n) {
int r = 1;
for (int i = 2; i <= n; ++i) r *= i;
return r;
}
C++98 里对应的是宏或 static const:
// C++98
#define SQUARE(x) ((x) * (x)) // 无类型检查,x 有副作用时会算两次
static const int N = 25;
迁移要点:
- 优先用
constexpr替换计算型宏; constexpr变量隐含const,且必须用常量表达式初始化;- 用在数组长度、模板实参、
static_assert场景时收益最明显; constexpr函数可以被普通运行时调用,无需两套实现。
三、using 替代 typedef
两者语义等价,但 using 可读性更好,而且支持模板别名(typedef 做不到):
// C++98
typedef std::map<std::string, std::vector<int> > Table;
typedef void (*Callback)(int, const char*);
// C++11
using Table = std::map<std::string, std::vector<int>>;
using Callback = void (*)(int, const char*);
// 模板别名:typedef 无法表达
template <class T>
using Vec = std::vector<T, MyAllocator<T>>;
另外 using 的别名声明位于左侧,长类型名一眼可读,也是函数指针类型的最佳写法。
四、委托构造与继承构造
委托构造(C++11)
多个构造函数共享初始化逻辑时,不必再抽一个 init():
class Conn {
public:
Conn() : Conn("127.0.0.1", 8080) {} // 委托
Conn(std::string host) : Conn(host, 8080) {} // 委托
Conn(std::string host, int port) : host_(std::move(host)), port_(port) {}
private:
std::string host_;
int port_;
};
限制:委托构造不能再同时初始化其他成员,初始化列表只能写一个目标构造函数。
继承构造(C++11)
struct Base {
Base(int);
Base(int, int);
};
struct Derived : Base {
using Base::Base; // 继承基类所有构造函数
};
省掉一堆转发样板代码,但注意:继承来的构造函数不能用于初始化派生类新增成员,需要时仍要自己写。
五、类内成员初始化(C++11)
class Buffer {
std::size_t size_ = 4096; // 就地初始化
bool dirty_ = false;
public:
Buffer() = default; // 使用默认值
explicit Buffer(std::size_t n) : size_(n) {}
};
规则:构造函数初始化列表的优先级高于类内默认值。这样可以把"默认值"集中写在声明处,构造函数只覆盖差异部分。
六、default 与 delete
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 明确禁止拷贝
NonCopyable& operator=(const NonCopyable&) = delete;
};
class Point {
public:
Point() = default; // 显式要求编译器生成默认构造
Point(int x, int y) : x_(x), y_(y) {}
private:
int x_ = 0, y_ = 0;
};
对比 C++98 的 private 声明 + 不实现 惯用法,= delete 在编译期就给出清晰错误,且可用于任意函数(包括重载决议中剔除不想要的隐式转换)。
七、范围 for 与 auto 组合
// C++98
for (std::map<std::string, int>::const_iterator it = m.begin(); it != m.end(); ++it) {
std::cout << it->first << " = " << it->second << "\n";
}
// C++11
for (const auto& kv : m) {
std::cout << kv.first << " = " << kv.second << "\n";
}
范围 for 的展开是 begin()/end() 调用,因此自定义类型只需提供 begin/end 即可被遍历。
八、迁移优先级建议
| 语法点 | 收益 | 风险 | 建议 |
|---|---|---|---|
nullptr | 消除重载歧义 | 极低 | 优先批量替换 NULL |
using 别名 | 可读性、支持模板别名 | 无 | 新代码一律用 using |
| 范围 for | 消除迭代器样板 | 低 | 逐步替换 |
constexpr | 编译期计算、类型安全 | 低 | 替换计算型宏 |
= delete / = default | 意图清晰 | 极低 | 顺手替换 |
| 类内初始化 | 减少构造函数重复 | 低 | 新类优先 |
这些改动都可以在保持 C++98 兼容风格的前提下分模块推进,不影响既有测试。