Skip to content

C++11 新特性概述

课程目标

  • 了解C++11相对于C++98的新特性
  • 通过示例理解这些新特性的实际应用与意义

1. 自动类型推导 - auto

1.1 简介

  • 使用auto关键字让编译器推断变量类型。

1.2 示例

cpp
auto i = 5;     // int
auto d = 3.14;  // double
auto str = "C++11";  // const char*

// 复杂类型推导
auto vec = std::vector<int>{1, 2, 3};

1.3 意义

  • 减少冗余代码,提高可读性,尤其是对于复杂类型如迭代器。

2. Lambda 表达式

2.1 简介

  • 定义匿名函数对象的能力,直接在需要使用函数的地方创建函数。

2.2 示例

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    // 定义一个简单的lambda表达式,它接受一个参数并打印它
    auto print = [](int n){ std::cout << n << " "; };
    
    // 创建一个vector并使用lambda打印每个元素
    std::vector<int> numbers = {5, 2, 9, 1, 5, 6};
    std::for_each(numbers.begin(), numbers.end(), print);
    std::cout << "\n";

    // 使用lambda进行条件过滤,捕获外部变量
    int threshold = 4;
    std::vector<int> filtered;
    std::copy_if(numbers.begin(), numbers.end(), 
                 std::back_inserter(filtered), 
                 [threshold](int x) { return x > threshold; });
    
    // 打印过滤后的结果
    std::cout << "Numbers greater than " << threshold << ": ";
    std::for_each(filtered.begin(), filtered.end(), print);
    std::cout << "\n";

    // 嵌套lambda - 计算每个元素的平方,然后求和
    int sumOfSquares = 0;
    std::for_each(numbers.begin(), numbers.end(), 
        [&sumOfSquares](int x) {
            sumOfSquares += [&x] {
                int square = x * x;
                return square; 
            }(); // 立即调用lambda计算平方
        });

    std::cout << "Sum of squares: " << sumOfSquares << std::endl;

    // Lambda作为比较函数用于排序
    std::sort(numbers.begin(), numbers.end(), 
        [](int a, int b) { return a > b; });  // 降序排序

    std::cout << "Sorted in descending order: ";
    std::for_each(numbers.begin(), numbers.end(), print);
    std::cout << "\n";

    return 0;
}

2.3 意义

  • 增强了函数作为参数的使用场景,使得代码更灵活、更简洁。

3. 右值引用与移动语义

3.1 简介

  • 引入了右值引用(&&),允许实现移动语义,避免不必要的拷贝。

3.2 示例

cpp
#include <iostream>
#include <cstring>
#include <utility>  // for std::move

class MyString {
private:
    char* m_data;
    size_t m_size;

public:
    // 常规构造函数
    MyString(const char* str = nullptr) {
        m_size = (str ? std::strlen(str) : 0);
        m_data = new char[m_size + 1];
        if (str) std::strcpy(m_data, str);
        else m_data[0] = '\0';
        std::cout << "Constructed: " << m_data << std::endl;
    }

    // 拷贝构造函数
    MyString(const MyString& other) {
        m_size = other.m_size;
        m_data = new char[m_size + 1];
        std::strcpy(m_data, other.m_data);
        std::cout << "Copy constructed: " << m_data << std::endl;
    }

    // **移动构造函数**
    MyString(MyString&& other) noexcept : m_data(nullptr), m_size(0) {
        m_data = other.m_data;  // 直接"steal" other的资源
        m_size = other.m_size;  
        other.m_data = nullptr; // other不再拥有这些资源
        other.m_size = 0;
        std::cout << "Move constructed: " << m_data << std::endl;
    }

    // 析构函数
    ~MyString() {
        delete[] m_data;
    }

    // **移动赋值操作符**
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] m_data;  // 释放现有资源
            m_data = other.m_data;
            m_size = other.m_size;
            other.m_data = nullptr;
            other.m_size = 0;
        }
        std::cout << "Move assignment: " << m_data << std::endl;
        return *this;
    }

    // 普通赋值操作符(简化版,不考虑自赋值问题)
    MyString& operator=(const MyString& other) {
        if(this != &other) {
            delete[] m_data;
            m_size = other.m_size;
            m_data = new char[m_size + 1];
            std::strcpy(m_data, other.m_data);
            std::cout << "Copy assignment: " << m_data << std::endl;
        }
        return *this;
    }

    void print() const {
        std::cout << (m_data ? m_data : "nullptr") << std::endl;
    }

};

// 辅助函数,用于展示右值引用
MyString createString() {
    return MyString("Temporary String");
}

int main() {
    MyString str1("Hello");
    MyString str2(str1);            // 拷贝构造
    str1.print();                   // 验证str1未改变

    MyString str3 = createString(); // 移动构造,因为createString()返回一个临时对象(右值)
    str3.print();

    MyString str4;
    str4 = std::move(str3);         // 移动赋值, str3将不再拥有其资源
    str4.print();
    str3.print();                   // str3现在应该指向nullptr

    return 0;
}

3.3 意义

  • 提高性能,特别是对于含有大量资源的对象。

4. 标准库的改进

4.1 智能指针

  • std::shared_ptr, std::unique_ptr, std::weak_ptr

4.2 示例

cpp
#include <iostream>
#include <memory>
#include <vector>

class Resource {
public:
    Resource() { 
        std::cout << "Resource acquired" << std::endl; 
    }

    ~Resource() { 
        std::cout << "Resource destroyed" << std::endl; 
    }

    void use() {
        std::cout << "Resource is being used." << std::endl;
    }
};

// 函数返回 shared_ptr 
std::shared_ptr<Resource> createResource() {
    // 使用 std::make_shared 比直接使用 new 更为高效
    return std::make_shared<Resource>();
}

void shareResource(std::shared_ptr<Resource> res) {
    if(res)
        res->use();
    // 'res' 超出作用域时,它的引用计数自动减1
}

int main() {
    {
        std::shared_ptr<Resource> mainRes;
        {
            // 创建 shared_ptr 对象 
            auto res = createResource();
            res->use();

            // 复制 shared_ptr, 增加引用计数
            mainRes = res;

            // 传递 shared_ptr 
            shareResource(res);
        } // 这里 res 超出作用域,但资源未被销毁,因为 mainRes 还在使用

        mainRes->use();
    } // mainRes 超出作用域,引用计数变为0,资源被销毁

    // 使用 vector 来管理多个 shared_ptr 
    std::cout << "\nDemonstrating shared_ptr in a vector:\n" << std::endl;
    std::vector<std::shared_ptr<Resource>> resources;
    for (int i = 0; i < 3; ++i) {
        resources.push_back(std::make_shared<Resource>());
    }

    for (auto &r : resources) {
        r->use();
    }
    // 离开这个作用域时,所有 shared_ptr 被销毁,相应的资源也被释放

    return 0;
}

4.3 意义

  • 自动内存管理,减少内存泄漏的风险。

5. 多线程支持

5.1 简介

  • 引入<thread>, <mutex>, <future>等支持多线程编程。

5.2 示例

cpp
#include <thread>
#include <chrono>

void task() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread done" << std::endl;
}

std::thread t(task);
t.join();

5.3 意义

  • 提供了对并发编程的原生支持,简化了多线程程序的编写。

6. 其他重要特性

  • 统一初始化:使用{}进行初始化。

  • decltype:获取变量或表达式类型。

    cpp
    #include <iostream>
    #include <vector>
    #include <typeinfo>
    
    int main() {
      std::vector<int> vec = {1, 2, 3, 4, 5};
      auto it = vec.begin(); // it 的类型为 std::vector<int>::iterator
      decltype(it) it2 = vec.end(); // it2 的类型也被推导为 std::vector<int>::iterator
    
      std::cout << "Type of it: " << typeid(it).name() << std::endl;
      std::cout << "Type of it2: " << typeid(it2).name() << std::endl;
    
      return 0;
    }
  • nullptr:一个表示空指针的关键字。

  • Range-based for loop:更简洁的遍历容器。

6.1 示例(Range-based for loop)

cpp
std::vector<int> vec = {1, 2, 3, 4, 5};
for (const auto &value : vec) {
    std::cout << value << ' ';
}

6.2 意义

  • 增强代码的表达力和安全性。

课堂练习

  • 编写一个使用autodecltype的程序。
  • 实现一个简单的lambda函数来排序一个vector。
  • 使用右值引用实现一个自己的字符串类。

总结回顾

  • C++11为现代C++编程带来了大量的便利和性能提升。
  • 通过这些特性,可以写出更安全、更高效的代码。

作业:

  1. 创建一个类 FileManager,该类使用std::shared_ptr来管理std::fstream对象。提供方法来打开文件,读取文件内容,写入文件,并确保在类销毁时文件被正确关闭。

    cpp
    class FileManager {
    public:
        // 构造函数、析构函数等
    private:
        std::shared_ptr<std::fstream> fileStream;
    };
    
    int main() {
        FileManager fm("example.txt");
        // 演示文件操作
    }

参考实现如下:

cpp
#include <iostream>
#include <fstream>
#include <memory>
#include <string>

class FileManager {
public:
    explicit FileManager(const std::string& filename) : filename_(filename) {
        openFile();
    }

    ~FileManager() {
        if(fileStream_ && fileStream_->is_open()) {
            fileStream_->close();
        }
    }

    void openFile() {
        fileStream_ = std::make_shared<std::fstream>(filename_, std::ios::in | std::ios::out | std::ios::app);
        if(!fileStream_->is_open()) {
            throw std::runtime_error("Could not open the file: " + filename_);
        }
    }

    std::string readContent() {
        if(!ensureFileOpen()) return "";
        std::string content;
        fileStream_->seekg(0, std::ios::end);
        content.resize(fileStream_->tellg());
        fileStream_->seekg(0, std::ios::beg);
        fileStream_->read(&content[0], content.size());
        return content;
    }

    void writeContent(const std::string& content) {
        if(!ensureFileOpen()) return;
        fileStream_->seekp(0, std::ios::end);
        *fileStream_ << content;
    }

private:
    bool ensureFileOpen() {
        if(!fileStream_ || !fileStream_->is_open()) {
            openFile();
        }
        return fileStream_->is_open();
    }

    std::string filename_;
    std::shared_ptr<std::fstream> fileStream_;
};

int main() {
    try {
        // 创建FileManager对象
        FileManager fm("example.txt");

        fm.writeContent("Hello, C++11 World!\n");
        
        std::string content = fm.readContent();
        std::cout << "File content:\n" << content << std::endl;
        
        fm.writeContent("Adding more content.");
        
        content = fm.readContent();
        std::cout << "Updated file content:\n" << content << std::endl;

    } catch(const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }

    return 0;
}