Skip to content

第二章 语言核心特性 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=3
cpp
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.0
cpp
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;
}