【C++标准库】

1.std::move

是 C++11 中引入的一个函数模板,用于将一个左值转换为右值引用。它的定义如下:

template<typename T>
typename remove_reference<T>::type&& move(T&& arg) noexcept;

其中,T&& 表示右值引用,noexcept 表示该函数不会抛出异常。std::move 的作用是将一个左值转换为右值引用,从而实现移动语义。移动语义是指,将一个对象的资源所有权从一个对象转移到另一个对象,而不是进行复制操作。移动语义可以避免复制大量数据的开销,提高程序的效率。

使用 std::move 的示例代码如下:

#include <iostream>
#include <vector>
#include <string>
  
int main() {
    std::vector<std::string> v1 {"hello", "world"};
    std::vector<std::string> v2 = std::move(v1);
    std::cout << "v1.size() = " << v1.size() << std::endl;
    std::cout << "v2.size() = " << v2.size() << std::endl;
    return 0;
}
//输出结果应该为:
//v1.size() = 0
//v2.size() = 2

在上面的代码中,我们定义了两个 std::vector < std :: string> 类型的对象 v1 和 v2,并使用 std::move 函数将 v1 的所有权转移给 v2。由于 std::vector 类型的对象是动态分配的,转移所有权相当于将 v1 中的内存地址和数据交给了 v2,而不是进行复制操作。因此,当 v1 的所有权被转移后,v1 中的数据已经不存在了,v1.size() 的结果应该为 0,而 v2 中的数据和 v1 中的数据一样,v2.size() 的结果应该为 2。

可以看到,使用 std::move 可以实现移动语义,避免了复制大量数据的开销,提高了程序的效率。需要注意的是,使用 std::move 时需要确保对象的状态是合法的,并且在转移所有权后,原来的对象不应该再访问该资源。

2.std::function

std::function 是一个通用的函数封装类模板,可以用于存储、复制和调用任意可调用对象(函数、函数指针、仿函数、Lambda表达式等)。

std::function 的定义方式如下:

template<class R, class... Args>
class function<R(Args...)>;

其中,R 表示返回值类型,Args… 表示参数类型列表。

std::function 的主要功能是将可调用对象封装成一个函数对象,并提供函数调用运算符 (),使得可以像调用普通函数一样调用这个函数对象。std::function 还可以进行赋值、拷贝、比较和空值检查等操作。

以下是一个使用 std::function 的示例代码:

#include <iostream>
#include <functional>
 
int add(int a, int b) {
    return a + b;
}
 
class MyFunctor {
public:
    int operator()(int a, int b) {
        return a - b;
    }
};
 
int main() {
    // 调用函数
    std::function<int(int, int)> f1 = add;
    std::cout << f1(1, 2) << std::endl;
 
    // 调用函数指针
    int (*p)(int, int) = add;
    std::function<int(int, int)> f2 = p;
    std::cout << f2(3, 4) << std::endl;
 
    // 调用仿函数
    MyFunctor f3;
    std::function<int(int, int)> f4 = f3;
    std::cout << f4(5, 6) << std::endl;
 
    // 调用Lambda表达式
    std::function<int(int, int)> f5 = [](int a, int b) { return a * b; };
    std::cout << f5(7, 8) << std::endl;
 
    return 0;
}

在上面的代码中,我们定义了一个名为 add 的函数、一个名为 MyFunctor 的仿函数、以及一个Lambda表达式,用于实现加法、减法和乘法运算。然后,我们使用 std::function 分别创建了 f1、f2、f4 和 f5 四个函数对象,并进行了函数调用。其中,f1 调用了函数 add,f2 调用了函数指针 p,f4 调用了仿函数 f3,f5 调用了Lambda表达式。最后,我们输出了函数调用的结果。

需要注意的是,使用 std::function 调用对象时,需要保证对象的函数签名(返回值类型和参数类型)与 std::function 的模板参数一致。

3.std::once_flag

std::call_once
std::once_flag 是 C++11 中的一个标准库类,用于保证某个函数只被执行一次。它通常与 std::call_once 函数一起使用,用于实现线程安全的单例模式。

#include <iostream>
#include <mutex>
 
class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag, []() { instance = new Singleton(); });
        return *instance;
    }
    void print() {
        std::cout << "Hello, Singleton!" << std::endl;
    }
private:
    Singleton() {}
    static Singleton* instance;
    static std::once_flag initFlag;
};
 
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;
 
int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << std::boolalpha << (&s1 == &s2) << std::endl; // 输出 true
    s1.print(); // 输出 "Hello, Singleton!"
    return 0;
}

在上面的代码中,我们定义了一个单例类 Singleton,并使用 std::once_flag 和 std::call_once 函数来保证该类只被初始化一次。在 getInstance() 函数中,我们使用 std::call_once 函数来确保 init() 函数只被执行一次,并在其中初始化单例对象。在 init() 函数中,我们使用 new 运算符来分配内存,并在静态成员变量 instance 中保存单例对象的指针。

需要注意的是,std::once_flag 和 std::call_once 可以保证线程安全的单例模式,但并不是最高效的实现方式。在多线程环境下,静态变量的初始化可能会导致锁的竞争和延迟,从而降低程序的效率和性能。因此,在实际编程中,需要根据具体情况选择合适的单例模式实现方式。

4.std::condition_variable

下面是一个使用 std::condition_variable 实现生产者-消费者模型的例子:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
 
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
 
void producer() {
    for (int i = 1; i <= 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        q.push(i); // 生产数据
        cv.notify_one(); // 通知消费者线程
    }
}
 
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        while (q.empty()) { // 等待队列非空
            cv.wait(lock);
        }
        int data = q.front(); // 消费数据
        q.pop();
        std::cout << "Consumer: " << data << std::endl;
    }
}
 
int main() {
    std::thread t1(producer); // 创建生产者线程
    std::thread t2(consumer); // 创建消费者线程
    t1.join();
    t2.join();
    return 0;
}

在上面的代码中,我们定义了一个互斥锁 mtx 和一个条件变量 cv,并使用一个队列 q 来共享数据。在生产者线程中,我们使用 q.push(i) 生产数据,并使用 cv.notify_one() 通知消费者线程。在消费者线程中,我们使用 cv.wait(lock) 等待队列非空,并在队列非空时消费数据。在消费数据后,我们使用 q.pop() 弹出数据,并输出消费的结果。

需要注意的是,在使用条件变量时,需要在互斥锁的保护下进行等待和通知操作。在等待条件时,需要使用 cv.wait(lock) 将线程阻塞,并释放互斥锁,以允许其他线程访问共享资源。在通知等待线程时,需要使用 cv.notify_one() 或 cv.notify_all() 通知等待线程,并让其重新竞争互斥锁。在使用条件变量时,需要特别小心死锁和资源竞争等问题,需要采取适当的同步措施,例如使用互斥锁和条件变量等,以保证线程间的安全和正确性。

5. std::lock_guard

std::unique_lock

std::lock_guard 和 std::unique_lock 都是 C++ 标准库中用于管理互斥锁的 RAII(Resource Acquisition Is Initialization)类,用于在获取互斥锁时自动加锁,并在对象生命周期结束时自动解锁互斥锁。

两者的主要区别在于灵活性和可扩展性。具体来说:

std::lock_guard 是一个轻量级的 RAII 类,无法手动释放互斥锁,也无法进行延迟加锁和解锁等操作。因此,std::lock_guard 更适合简单的互斥锁管理场景,例如在函数内部获取互斥锁,并在函数返回时自动释放互斥锁。
std::unique_lock 是一个灵活的 RAII 类,可以手动释放互斥锁,并支持延迟加锁和解锁等操作。因此,std::unique_lock 更适合复杂的互斥锁管理场景,例如需要在函数内部多次获取和释放互斥锁,或需要支持条件变量等高级特性。
以下是两者的使用示例:

#include <mutex>
 
// 使用 std::lock_guard 管理互斥锁
void foo() {
    std::mutex mtx;
    {
        std::lock_guard<std::mutex> lock(mtx); // 获取互斥锁
        // 临界区代码
    } // 自动释放互斥锁
}
 
// 使用 std::unique_lock 管理互斥锁
void bar() {
    std::mutex mtx;
    {
        std::unique_lock<std::mutex> lock(mtx); // 获取互斥锁
        // 临界区代码
        lock.unlock(); // 手动释放互斥锁
        // 非临界区代码
        lock.lock(); // 手动获取互斥锁
        // 临界区代码
    } // 自动释放互斥锁
}

需要注意的是,在使用 RAII 类管理互斥锁时,必须避免手动释放互斥锁和多次获取和释放互斥锁等问题,以避免出现竞态条件和数据不一致等问题。同时,在使用条件变量等高级特性时,需要使用 std::unique_lock 类来管理互斥锁,以便于支持延迟加锁和解锁等操作。

6.std::semaphore

信号量(Semaphore)是一种用于线程间同步和互斥的机制,它可以用来保护共享资源,避免出现竞态条件和数据不一致等问题。信号量包括计数型信号量和二进制信号量两种类型,其中计数型信号量的计数值可以大于 1,而二进制信号量的计数值只能是 0 或 1。

在 C++ 标准库中,可以使用 std::semaphore 类来实现信号量,该类定义在 头文件中,是 C++20 新增的标准库特性。std::semaphore 类提供了 wait() 和 post() 两个成员函数,分别用于等待信号量和释放信号量。例如,以下代码实现了一个计数型信号量:

#include <semaphore>
#include <iostream>
 
std::semaphore sem(0); // 创建计数型信号量,计数值为 0
 
void producer() {
    for (int i = 1; i <= 10; ++i) {
        // 生产数据
        std::cout << "Producing data: " << i << std::endl;
        // 释放信号量
        sem.post();
    }
}
 
void consumer() {
    for (int i = 1; i <= 10; ++i) {
        // 等待信号量
        sem.wait();
        // 消费数据
        std::cout << "Consuming data: " << i << std::endl;
    }
}
 
int main() {
    std::thread t1(producer); // 创建生产者线程
    std::thread t2(consumer); // 创建消费者线程
    t1.join();
    t2.join();
    return 0;
}

在上面的代码中,我们创建了一个计数型信号量 sem,初始计数值为 0。在生产者线程中,每次生产完数据后,调用 sem.post() 函数释放一个信号量,从而通知消费者线程可以消费数据了。在消费者线程中,调用 sem.wait() 函数等待一个信号量,从而等待生产者线程生产新的数据。当有新的数据可用时,生产者线程会释放一个信号量,消费者线程会获取一个信号量,并消费相应的数据。通过信号量的等待和释放,我们可以实现线程间的同步和互斥,保证程序的正确性和安全性。

需要注意的是,在使用 std::semaphore 类时,需要使用 #include 头文件,并链接 -pthread 选项以启用 POSIX 线程库。同时,需要注意正确的信号量计数值和信号量的等待和释放操作,以避免出现死锁和竞态条件等问题。

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值