第二章 语言核心特性 v1.0
一、结构化绑定 (Structured Bindings)
结构化绑定是C++17中最受欢迎的特性之一,它允许将一个结构体、数组或元组解包到多个变量中,大大简化了多值返回和复杂数据结构的处理。
1.1 基本语法
cpp
// 基本语法形式
auto [var1, var2, var3] = expression;1.2 应用场景
cpp
int arr[] = {1, 2, 3};
auto [a, b, c] = arr; // a=1, b=2, c=3cpp
struct Point {
double x;
double y;
double z;
};
Point p{1.0, 2.0, 3.0};
auto [x, y, z] = p; // x=1.0, y=2.0, z=3.0cpp
std::map<std::string, int> ages = {{"Alice", 25}, {"Bob", 30}};
for (const auto& [name, age] : ages) {
std::cout << name << " is " << age << " years old\n";
}cpp
std::tuple<int, std::string, double> getData() {
return {42, "answer", 3.14};
}
auto [id, name, value] = getData();1.3 自定义类型的结构化绑定
要让自定义类型支持结构化绑定,需要提供相应的get函数:
cpp
class Employee {
private:
std::string name;
int id;
double salary;
public:
Employee(std::string n, int i, double s) : name(n), id(i), salary(s) {}
std::string getName() const { return name; }
int getId() const { return id; }
double getSalary() const { return salary; }
};
// 提供结构化绑定支持
namespace std {
template<>
struct tuple_size<Employee> {
static constexpr size_t value = 3;
};
template<size_t I>
struct tuple_element<I, Employee> {
using type = std::conditional_t<I == 0, std::string,
std::conditional_t<I == 1, int, double>>;
};
}
template<size_t I>
auto get(const Employee& e) {
if constexpr (I == 0) return e.getName();
else if constexpr (I == 1) return e.getId();
else return e.getSalary();
}
// 使用
Employee emp("John", 1001, 5000.0);
auto [name, id, salary] = emp;cpp
class Product {
private:
std::string name;
double price;
int quantity;
public:
Product(std::string n, double p, int q) : name(n), price(p), quantity(q) {}
// ADL get函数
friend auto get(const Product& p) {
return std::make_tuple(p.name, p.price, p.quantity);
}
};
// 使用
Product prod("Laptop", 999.99, 10);
auto [name, price, quantity] = prod;二、if constexpr - 编译时条件判断
if constexpr允许在编译时根据条件选择性地编译代码分支,这对于模板编程特别有用,可以避免实例化不合适的模板。
2.1 基本语法
cpp
if constexpr (condition) {
// 如果condition为true,编译这段代码
} else {
// 如果condition为false,编译这段代码
}2.2 应用场景
cpp
template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
// 整数类型处理
return value * 2;
} else if constexpr (std::is_floating_point_v<T>) {
// 浮点类型处理
return value + 1.0;
} else {
// 其他类型处理
return value;
}
}
int main() {
std::cout << process(10) << std::endl; // 输出: 20
std::cout << process(3.14) << std::endl; // 输出: 4.14
std::cout << process("hello") << std::endl; // 输出: hello
}cpp
template<typename T>
void printTypeInfo(T value) {
if constexpr (std::is_pointer_v<T>) {
std::cout << "Pointer type, points to: " << *value << std::endl;
} else {
std::cout << "Non-pointer type: " << value << std::endl;
}
}
int main() {
int x = 42;
printTypeInfo(x); // 输出: Non-pointer type: 42
printTypeInfo(&x); // 输出: Pointer type, points to: 42
// 如果没有if constexpr,对非指针类型调用*value会导致编译错误
}cpp
#include <type_traits>
#include <utility>
template<typename T, typename = void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T, std::void_t<
decltype(std::declval<const T&>().size())
>> : std::true_type {};
template<typename T>
auto getLength(const T& container) {
static_assert(has_size<T>::value, "container must provide size()");
return container.size();
}
requires表达式和 Concepts 是 C++20 的内容。C++17 中要判断成员是否存在,仍然需要使用std::void_t一类的检测惯用法。
三、类模板参数推导 (Class Template Argument Deduction)
C++17允许编译器根据构造函数的参数自动推导类模板的参数类型,使代码更加简洁。
3.1 基本用法
cpp
std::pair<int, std::string> p1{1, "hello"};
std::vector<int> v1{1, 2, 3};
std::lock_guard<std::mutex> lock(m);cpp
std::pair p1{1, "hello"}; // 推导为std::pair<int, const char*>
std::vector v1{1, 2, 3}; // 推导为std::vector<int>
std::lock_guard lock(m); // 推导为std::lock_guard<std::mutex>3.2 自定义推导指南
可以为自定义类模板提供推导指南,控制参数推导的行为:
cpp
template<typename T>
class Container {
private:
T data;
public:
Container(T value) : data(value) {}
T get() const { return data; }
};
// 自定义推导指南
Container(const char*) -> Container<std::string>;
// 使用
Container c1{42}; // 推导为Container<int>
Container c2{"hello"}; // 推导为Container<std::string>(使用自定义推导指南)3.3 复杂类型的推导
cpp
// std::tuple的推导
std::tuple t1{1, 2.0, "three"}; // 推导为std::tuple<int, double, const char*>
// std::array的推导
std::array a1{1, 2, 3, 4, 5}; // 推导为std::array<int, 5>
// std::function在C++17中不能通过CTAD推导,需要写出函数签名
std::function<int(int)> f1 = [](int x) { return x * 2; };四、内联变量 (Inline Variables)
C++17引入了内联变量,解决了在头文件中定义变量时的多重定义问题。
4.1 基本用法
cpp
// 头文件 header.h
#pragma once
inline int globalCounter = 0; // 内联变量,可以在多个翻译单元中定义
class MyClass {
public:
static inline int staticMember = 42; // 内联静态成员变量
};4.2 应用场景
cpp
// constants.h
#pragma once
inline const double PI = 3.14159265358979323846;
inline const std::string APP_NAME = "MyApplication";
inline const int MAX_CONNECTIONS = 100;cpp
class Singleton {
private:
Singleton() = default;
public:
static inline Singleton& getInstance() {
static Singleton instance;
return instance;
}
void doSomething() { /* ... */ }
};cpp
template<typename T>
struct TypeInfo {
static inline const char* name = "Unknown";
};
template<>
struct TypeInfo<int> {
static inline const char* name = "int";
};
template<>
struct TypeInfo<double> {
static inline const char* name = "double";
};五、新的属性 (Attributes)
C++17引入了几个新的属性,帮助开发者更好地表达代码意图和优化编译器行为。
5.1 [[maybe_unused]]
用于标记可能未使用的变量、函数或类型,避免编译器产生警告。
cpp
int compute(int x, [[maybe_unused]] int y) {
// y参数可能未使用,但不产生警告
return x * 2;
}
void process() {
[[maybe_unused]] int debugValue = 42; // 可能未使用的变量
// 在调试版本中使用,发布版本中不使用
#ifdef DEBUG
std::cout << "Debug: " << debugValue << std::endl;
#endif
}5.2 [[nodiscard]]
用于标记函数返回值,如果调用者忽略返回值,编译器会产生警告。
cpp
[[nodiscard]] int computeResult() {
return 42;
}
// 使用
int result = computeResult(); // 正确
computeResult(); // 警告:忽略返回值
// 也可以应用于类
class [[nodiscard]] Result {
// ...
};
Result getResult(); // 忽略返回值会产生警告5.3 [[fallthrough]]
用于标记switch语句中的故意穿透,避免编译器警告。
cpp
void process(int value) {
switch (value) {
case 0:
std::cout << "Zero\n";
[[fallthrough]]; // 故意穿透到case 1
case 1:
std::cout << "Zero or One\n";
break;
case 2:
std::cout << "Two\n";
break;
default:
std::cout << "Other\n";
break;
}
}六、其他语言改进
6.1 嵌套命名空间定义
C++17简化了嵌套命名空间的定义语法。
cpp
// C++17之前
namespace A {
namespace B {
namespace C {
void function() {}
}
}
}
// C++17新语法
namespace A::B::C {
void function() {}
}6.2 noexcept
动态异常规范 throw(type) 在 C++17 中被移除;实际代码中使用 noexcept 表示函数不会抛出异常。
cpp
void newFunction() noexcept; // 不抛出异常
void conditionalFunction() noexcept(false); // 可能抛出异常
// 条件noexcept
template<typename T>
void process(T value) noexcept(std::is_nothrow_copy_constructible_v<T>) {
T copy = value; // 如果T的拷贝构造函数不抛出异常,则整个函数不抛出异常
}七、实际应用示例
7.1 综合示例:配置解析器
cpp
#include <string>
#include <variant>
#include <optional>
#include <vector>
#include <map>
#include <iostream>
// 使用C++17特性实现的简单配置解析器
class ConfigParser {
private:
using ConfigValue = std::variant<int, double, std::string, bool>;
std::map<std::string, ConfigValue> config;
public:
// 使用结构化绑定解析键值对
void parseLine(const std::string& line) {
if (line.empty() || line[0] == '#') return; // 忽略空行和注释
auto [key, value] = parseKeyValue(line);
if (key.empty()) return;
// 使用if constexpr进行类型推导
auto parsedValue = parseValue(value);
config[key] = parsedValue;
}
// 使用std::optional返回可能不存在的值
template<typename T>
std::optional<T> getValue(const std::string& key) const {
auto it = config.find(key);
if (it == config.end()) {
return std::nullopt;
}
// 使用std::get_if安全访问variant
if (auto value = std::get_if<T>(&it->second)) {
return *value;
}
return std::nullopt;
}
private:
std::pair<std::string, std::string> parseKeyValue(const std::string& line) {
auto pos = line.find('=');
if (pos == std::string::npos) return {"", ""};
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// 简单的去除空格
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
return {key, value};
}
ConfigValue parseValue(const std::string& value) {
// 尝试解析为bool
if (value == "true" || value == "false") {
return value == "true";
}
// 尝试解析为整数
try {
size_t pos;
int intValue = std::stoi(value, &pos);
if (pos == value.size()) {
return intValue;
}
} catch (...) {
// 不是整数,继续尝试其他类型
}
// 尝试解析为浮点数
try {
size_t pos;
double doubleValue = std::stod(value, &pos);
if (pos == value.size()) {
return doubleValue;
}
} catch (...) {
// 不是浮点数,作为字符串处理
}
// 默认作为字符串处理
return value;
}
};
// 使用示例
int main() {
ConfigParser parser;
std::vector<std::string> configLines = {
"# 配置文件示例",
"port = 8080",
"timeout = 30.5",
"debug = true",
"name = MyServer"
};
for (const auto& line : configLines) {
parser.parseLine(line);
}
// 使用结构化绑定和std::optional
if (auto port = parser.getValue<int>("port")) {
std::cout << "Port: " << *port << std::endl;
}
if (auto timeout = parser.getValue<double>("timeout")) {
std::cout << "Timeout: " << *timeout << std::endl;
}
if (auto debug = parser.getValue<bool>("debug")) {
std::cout << "Debug: " << std::boolalpha << *debug << std::endl;
}
if (auto name = parser.getValue<std::string>("name")) {
std::cout << "Name: " << *name << std::endl;
}
return 0;
}