C++线程异步

本文内容来自:

智谱清言

《深入应用C++11 代码优化与工程级应用》 

std::future
std::future作为异步结果的传输通道,可以很方便地获取线程函数的返回值。
 std::future_status
  1. Ready (std::future_status::ready):

    • 当与 std::future 对象关联的异步操作已经完成时,std::future 处于 ready 状态。
    • 在这个状态下,调用 get()wait() 或 wait_for() 将立即返回,并且 get() 将返回操作的结果(或者抛出异常,如果操作以异常结束)。   
  2. Timeout (std::future_status::timeout):

    • 当调用 wait_for() 或 wait_until() 并且指定的等待时间已经过去,但异步操作尚未完成时,std::future 处于 timeout 状态。
    • 这意味着 wait_for() 或 wait_until() 调用返回了 std::future_status::timeout,表明等待超时,但 std::future 仍然可能在未来某个时间点变为 ready 状态。
  3. Deferred (std::future_status::deferred):

    • 当与 std::future 对象关联的异步操作是延迟执行的(即,它将在调用 get()wait()时才执行),std::future 处于 deferred 状态。
    • 这种情况发生在使用 std::async 时指定了 std::launch::deferred,这意味着操作将在第一次调用 get()wait() 或 wait_for() 时在当前线程上执行。 

状态转换图:

std::future 对象在其生命周期内只能从 deferred 状态转移到 ready 状态,或者从 ready 状态转移到 timeout 状态(仅在调用 wait_for() 或 wait_until() 时)。一旦 std::future 对象处于 ready 状态,它将保持这种状态直到其结果被获取或对象被销毁。如果异步操作在调用 wait_for() 或 wait_until() 之前完成,那么即使等待超时,std::future 对象也会处于 ready 状态。  

std::future::get() 

 std::future::wait

当使用 std::asyncstd::packaged_task 或 std::promise 创建异步任务时,你可以通过 std::future 对象来获取结果。std::future 提供了 wait 函数,用于阻塞当前线程,直到与 future 对象关联的共享状态变为就绪状态。

std::future::wait 的行为与 std::future::get 类似,但 wait 不会返回结果,它只是等待操作完成。如果你只是想确保操作完成而不需要立即获取结果,使用 wait 是合适的。 

需要注意的是,如果你在一个已经就绪的 future 上调用 wait,它将立即返回,而不会阻塞。如果你在一个处于 deferred 状态的 future 上调用 wait,它将执行关联的任务并等待其完成。 

#include <future>
#include <iostream>int main() {std::future<int> fut = std::async(std::launch::async, []() {// 异步执行一些操作return 42;});// 等待 future 完成fut.wait();// 获取结果std::cout << "Result: " << fut.get() << std::endl;return 0;
}
std::future::wait_for

它允许你等待一个 future 对象指定的时长。如果 future 在这段时间内变为就绪状态,wait_for 将返回 std::future_status,表明 future 的状态。

测试1: 
#include <future>
#include <iostream>
#include <chrono>int main() {// 创建一个异步任务std::future<int> fut = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟耗时操作return 42;});// 等待 future 完成,最多等待 3 秒auto status = fut.wait_for(std::chrono::seconds(3));if (status == std::future_status::ready) {std::cout << "Future is ready. Result: " << fut.get() << std::endl;} else if (status == std::future_status::timeout) {std::cout << "Future is not ready within the given time." << std::endl;} else if (status == std::future_status::deferred) {std::cout << "Future is deferred and will execute in the calling thread." << std::endl;}return 0;
}

 Future is not ready within the given time.

 在这个例子中,我们创建了一个异步任务,它将在 5 秒后返回结果。我们使用 wait_for 来等待 future 完成,但只等待 3 秒。因此,wait_for 将返回 std::future_status::timeout,表明 future 在指定的 3 秒内没有准备好。

如果我们将 wait_for 的等待时间增加到超过 5 秒,那么 wait_for 将返回 std::future_status::ready,因为异步任务将在等待时间结束前完成。

测试2: 

我自己跑了下,改成5s还是不行:

测试3:

6s可以

 如果任务是以延迟方式执行的(即 std::launch::deferred),那么 wait_for 将返回 std::future_status::deferred。 

------------- 

线程异步操作函数async

std::async可以用来直接创建异步的task,异步任务返回的结果保存在future中。

(1)需要获取异步任务的结果时,调用future.get()方法即可。

(2)不关注异步任务的结果,只是简单地等待任务完成的话,调用future.wait()方法。

std::async的第一个参数: std::launch::async | std::launch::deferred

 std::launch::async :在调用async时就开始创建线程

 std::launch::deferred :延迟加载方式创建线程。调用async时不创建线程,

 直到调用了 future的get或者wait时才创建线程。

 std::async的第二个参数:线程函数

 std::async的第三个参数:线程函数的参数

示例: 

#include <future>
#include <iostream>int main() {// 使用 std::async 创建一个异步任务std::future<int> fut = std::async(std::launch::async, []() {// 异步任务执行一些计算并返回结果return 42;});// 在这里,主线程可能会执行其他任务...// 获取异步任务的结果int result = fut.get(); // 这将阻塞,直到异步任务完成std::cout << "The result is " << result << std::endl;return 0;
}
launch:启动(计算机程序)
sync:同时,同步;协调,一致
std::promise

std::promise将数据与future绑定起来,为获取线程函数中的某个值提供便利,在线程函数中为外面传过来的promise赋值,在线程函数执行完成之后就可以通过promise的future获取该值了。

取值是通过promise内部提供的future来获取的。

promise:承诺

#include <future>
#include <iostream>
#include <chrono>int main() {std::promise<int> pr;std::thread t([](std::promise<int>& p) {// 执行一些操作...p.set_value_at_thread_exit(9); // 在线程退出时设置值}, std::ref(pr));std::future<int> f = pr.get_future();// 等待子线程完成t.join();// 现在可以安全地获取值auto r = f.get();std::cout << "Received: " << r << std::endl;return 0;
}

Received: 9

set_value_at_thread_exit

它允许你设置一个值,这个值将在当前线程退出时传递给与之关联的 std::future 对象

在当前线程退出时,应该将给定的值传递给 std::future。这可以确保即使线程在设置值后立即退出,相关的值也会被存储,并且可以在其他地方通过 std::future 对象获取。

  • 它在 std::promise 类中声明。
  • 它接受一个值,这个值可以是任何可复制的类型。
  • 它不会立即设置值,而是在当前线程退出时设置值。
  • 调用 set_value_at_thread_exit 后,std::promise 对象处于就绪状态,即 future::valid() 为 true
  • 一旦 set_value_at_thread_exit 被调用,你就不能再通过这个 std::promise 对象设置另一个值。

“任何可复制的类型” 指的是那些可以被复制构造函数或复制赋值运算符安全复制的类型。这意味着这些类型的对象可以被创建为其他同类对象的副本,而不会导致未定义行为。 

std::ref

 std::ref 用于创建一个包装器,这个包装器能够将引用传递给接受值传递的函数。这在需要将引用传递给像线程函数或函数对象这样的参数时非常有用。

以下是一些使用 std::ref 的例子: 

1. 将引用传递给线程函数

通常情况下,当创建一个线程时,你传递给 std::thread 构造函数的参数是通过值传递的。如果你想要传递一个引用,你可以使用 std::ref。

#include <iostream>
#include <thread>
#include <functional>void func(int& n) {n++;
}int main() {int n = 0;std::thread t(func, std::ref(n));t.join();std::cout << "n = " << n << std::endl; // 输出 n = 1return 0;
}

在上面的例子中,std::ref(n) 创建了一个引用包装器,使得 func 函数能够通过引用修改 n

--------- 

2. 将引用传递给函数对象

std::ref 也可以用于将引用传递给函数对象。

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>struct Increment {void operator()(int& n) {n++;}
};int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::for_each(vec.begin(), vec.end(), Increment());std::for_each(vec.begin(), vec.end(), Increment());for (int n : vec) {std::cout << n << ' '; // 输出 3 4 5 6 7}std::cout << std::endl;return 0;
}

std::for_each 接受一个函数对象 Increment,并且因为 Increment 接受一个引用参数,所以不需要使用 std::ref。但是,如果函数对象是通过值传递参数的,使用 std::ref 可以确保传递的是引用。 

---------- 

记住,std::ref 只在需要引用语义时使用。如果你不需要修改原始对象,或者函数接受的是按值传递的参数,那么使用 std::ref 是不必要的。 

--------- 

std::for_each

 std::for_each 是 C++ 标准库中的一个算法,它用于对容器中的每个元素执行一个操作。这个算法会遍历指定范围内的所有元素,并对每个元素调用提供的函数或函数对象。

 template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f ); 

unary:一元的 

  • InputIt first, InputIt last: 这两个参数定义了要遍历的元素范围。通常,它们是容器的迭代器。
  • UnaryFunction f: 这是一个函数或函数对象,它将被应用于范围内的每个元素。这个函数或函数对象应该接受一个参数,对应于范围内的每个元素.

由于我们正在修改容器中的元素,迭代器必须是能够修改元素的类型,即随机访问迭代器。在 std::vector 中,begin() 和 end() 返回的迭代器就是这种类型。 

 std::for_each 是 C++ 标准库中的一个算法,它用于对容器中的每个元素执行一个操作。这个算法会遍历指定范围内的所有元素,并对每个元素调用提供的函数或函数对象。

 template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f ); 

unary:一元的 

  • InputIt first, InputIt last: 这两个参数定义了要遍历的元素范围。通常,它们是容器的迭代器。
  • UnaryFunction f: 这是一个函数或函数对象,它将被应用于范围内的每个元素。这个函数或函数对象应该接受一个参数,对应于范围内的每个元素.

由于我们正在修改容器中的元素,迭代器必须是能够修改元素的类型,即随机访问迭代器。在 std::vector 中,begin() 和 end() 返回的迭代器就是这种类型。 

----------

传递给 std::thread 构造函数的参数默认是通过值传递的

 当创建一个 std::thread 对象时,传递给 std::thread 构造函数的参数默认是通过值传递的。这意味着,如果传递的是非引用类型,那么线程函数将会接收到传递参数的一个副本。

#include <iostream>
#include <thread>void threadFunction(int n) {// 这里我们接收到的是 n 的副本n += 100;std::cout << "Inside thread: " << n << std::endl;
}int main() {int n = 1;std::thread t(threadFunction, n); // 传递 n 的副本t.join();std::cout << "Outside thread: " << n << std::endl; // n 仍然是 1return 0;
}

 如果我们想要在线程函数中修改原始变量,就需要传递一个引用。但是,由于 std::thread 构造函数默认是按值传递的,因此需要使用 std::ref 来传递引用

#include <iostream>
#include <thread>
#include <functional>void threadFunction(int& n) {n += 100;std::cout << "Inside thread: " << n << std::endl;
}int main() {int n = 1;std::thread t(threadFunction, std::ref(n)); // 使用 std::ref 传递引用t.join();std::cout << "Outside thread: " << n << std::endl; // n 现在是 101return 0;
}

 这就是 std::ref 的用途所在,它允许在线程或其他按值传递参数的上下文中传递引用。

---------

线程退出

线程的退出时机取决于线程的执行逻辑和线程的创建方式。以下是几种情况下线程可能退出的时机:

  1. 任务完成退出:线程执行的任务完成后,线程函数(无论是普通函数、成员函数还是 lambda 表达式)会执行返回语句。当线程函数返回时,线程会自然退出。这是线程退出的最常见方式。

  2. 自毁退出:在 C++ 中,当 std::thread 对象被销毁时,如果关联的线程仍在运行,std::thread 的析构函数会调用 std::terminate 来终止程序,除非:

    • 线程已经完成了它的执行(任务完成)。
    • 线程是通过 std::detach 被分离的。
    • 线程被 join 过,且 std::thread 对象是通过移动构造或移动赋值创建的。
  3. 分离退出:如果线程被分离(使用 std::thread::detach),它将在后台运行,与创建它的 std::thread 对象无关。线程将在执行完毕后自动退出,资源被系统回收。

  4. 异常退出:如果线程执行的函数抛出了未捕获的异常,并且没有设置相应的异常处理机制,线程将异常退出。

  5. 外部干预退出:线程可以被外部干预强制退出,例如:

    • 调用 pthread_cancel(在 POSIX 线程中)来请求取消线程。
    • 在 Windows 中,可以通过调用 TerminateThread 或 ExitThread 来终止线程,但这通常是不安全的做法,因为它可能导致资源泄露和其他问题。
  6. 程序终止:当主线程退出时,如果其他线程是可连接的(joinable),程序通常会调用 std::terminate 来终止这些线程。如果其他线程是分离的,它们可能在主线程退出后继续运行,直到完成或被系统强制终止。

为了确保资源得到正确释放,线程应当尽可能通过自然的退出路径结束,例如任务完成或通过 join 等待线程结束。避免使用强制退出线程的方法,因为这可能会导致不一致的状态和资源泄露。

---------

  1. 移动构造和移动赋值:当一个 std::thread 对象是通过移动构造或移动赋值创建的,它会接手源对象所管理的线程,而源对象将不再管理任何线程(即变为非.joinable状态)。

  2. 被 join 过的 std::thread:如果一个 std::thread 对象已经调用了 join,那么它将不再管理任何线程,也就是说它变成了非.joinable状态。在这种情况下,再次调用 join 是不允许的,将会导致未定义行为。

---------

可调用对象的包装类std::package_task

package(包装)

std::package_task包装了一个可调用对象的包装类(如function,lambda expression,bind expression和another function object),将函数和future绑定起来,以便异步调用。

它和std::promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged_task保存的是一个函数。

它允许异步执行可调用对象,并能够获取其返回值或异常。

std::packaged_task 的主要用途是在多线程编程中,当你想要在另一个线程中执行某个任务,并随后获取该任务的执行结果时。

std::packaged_task 的一些基本用法:

创建 std::packaged_task

首先,你需要包含 <future> 头文件,然后创建一个 std::packaged_task 对象,并将其绑定到一个可调用对象上。

#include <future>
#include <iostream>int calculateSomething() {// 假设这里有一些计算return 42;
}int main() {std::packaged_task<int()> task(calculateSomething);return 0;
}
 获取 std::future

std::packaged_task 对象有一个 get_future() 方法,它返回一个 std::future 对象,该对象可以用来存储异步操作的结果。

std::future<int> result = task.get_future();
 执行任务

你可以通过直接调用 std::packaged_task 对象或将其传递给一个线程来执行任务

// 直接调用
task();// 或者在另一个线程中执行
std::thread thread(std::move(task));
thread.join();
 获取结果

在任务执行完毕后,你可以通过 std::future 对象获取结果。

int value = result.get(); // 这将阻塞直到任务完成
std::cout << "The result is " << value << std::endl;
示例:
#include <iostream>
#include <thread>
#include <utility>
#include <future>
#include <thread>int func (int x) { return x + 2; }int main()
{std::packaged_task<int(int)> tsk(func);std::future<int> fut = tsk.get_future();std::thread(std::move(tsk),2).detach();int value = fut.get();std::cout << "value:" << value << std::endl;
}

 value:4

 需要注意的是,使用.detach()后,线程将在后台运行,主线程不会等待它结束。

如果主线程结束,程序会终止,这可能会导致线程在完成其任务之前就被强制结束。

为了避免这种情况,通常建议在程序结束前确保所有线程都已完成其工作或者确保程序不会因为主线程的退出而结束。 

在这个例子中,由于fut.get()会等待任务完成,所以使用.detach()是安全的。

测试:

如果主线程结束,程序会终止,这可能会导致线程在完成其任务之前就被强制结束。

​#include <iostream>
#include <thread>
#include <QFile>
#include <QDebug>
void threadFunction() {std::this_thread::sleep_for(std::chrono::seconds(2));// 创建一个 QFile 对象,并指定文件名QFile file("example.txt");// 打开文件用于写入,如果文件不存在则创建它if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {// 使用 QTextStream 来简化向文件写入文本QTextStream out(&file);// 写入一些信息out << "Hello, World!" << endl;// 关闭文件file.close();} else {// 如果文件打开失败,打印错误信息qWarning("无法打开文件: %s", qPrintable(file.errorString()));}std::cout << "Thread finished execution." << std::endl;
}int main() {std::thread t(threadFunction);t.detach();  // 分离线程//t.join();std::cout << "Main thread will now exit." << std::endl;// 注意:这里没有等待分离的线程结束return 0;
}​

 结果:确实没有生成txt文件,也没有打印出"Hello,World!"

---------

 detach 是 std::thread 类的一个成员函数,用于将当前线程从其 std::thread 对象分离。一旦线程被分离,它将独立于其 std::thread 对象运行,并且不能再通过该对象与之交互

  1. 分离后的线程: 调用 detach 后,线程将在后台运行,这意味着主程序不需要等待该线程结束即可继续执行或退出。分离的线程在结束时将自动释放与其关联的所有资源。

  2. 无法再次连接: 一旦线程被分离,就不能再通过 std::thread 对象与之连接,也就是说,你不能使用 std::thread::join() 方法来等待线程结束。

  3. 资源管理: 当一个线程对象被销毁时,如果它仍然与一个执行线程相关联(即没有被分离或加入),程序将终止。因此,如果你不打算使用 join 来等待线程结束,你应该使用 detach 来避免资源泄漏和潜在的未定义行为。

  4. 线程结束: 即使创建该线程的 std::thread 对象被销毁,线程也会继续运行直到其任务函数完成。如果线程函数抛出异常,且该异常没有被捕获,程序将调用 std::terminate 终止。

  5. 使用场景detach 通常用于那些不需要返回值的后台任务,或者当你知道线程将在程序结束前完成其工作。

  6. 无法获取返回值:因为线程已经与 std::thread 对象断开连接,所以无法通过 std::thread 对象获取线程的返回值(间接)。

完整示例:
#include <future>
#include <iostream>
#include <thread>int calculateSomething() {std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作return 42;
}int main() {// 创建 packaged_taskstd::packaged_task<int()> task(calculateSomething);// 获取 future 对象std::future<int> result = task.get_future();// 在单独的线程中执行任务std::thread thread(std::move(task));// 主线程可以继续执行其他任务...// 获取结果int value = result.get(); // 阻塞直到任务完成std::cout << "The result is " << value << std::endl;// 等待线程结束thread.join();return 0;
}

 calculateSomething 函数在一个单独的线程中执行,并通过 std::future 获取其返回值。注意,当 result.get() 被调用时,如果任务尚未完成,它将阻塞调用线程,直到任务完成。

--------- 

上面使用了std::move

std::thread thread(std::move(task));

 std::move 是用来转移 task 对象的所有权。这意味着 task 变量将不再拥有包装的任务,而新创建的线程将拥有这个任务。在调用 std::move 后,task 对象将变为空,不能再次被使用。

---------

还可以使用std::ref

std::thread thread(std::ref(task));

std::ref 是用来传递 task 对象的引用给线程。这意味着 task 对象本身不会被移动,它在主线程和创建的新线程中都是可用的。在这种情况下,task 对象必须保持有效,直到新线程完成其执行,否则可能会导致未定义行为 

--------- 

 以下是对两者的简要比较:

  • std::move:适用于当你想要将 task 的所有权转移给新线程,并且不再在当前线程中使用 task 的情况。
  • std::ref:适用于当你需要在主线程和新线程中都保持对 task 的访问权的情况。

---------

需要注意的是,由于 std::packaged_task 不支持拷贝,所以在创建线程时不能直接传递 task(除非是传递它的引用)。如果你尝试这样做,编译器会报错,因为 std::thread 的构造函数需要一个可移动的参数。这就是为什么通常使用 std::move 或 std::ref 来传递 std::packaged_task 对象给 std::thread 的原因。

把std::packaged_task对象作为参数传递给std::thread对象时,因为std::packaged_task对象不支持拷贝,无法通过拷贝构造函数生成一个临时对象,所以编译器会报错。

---------

std::thread 的构造函数需要一个可移动的参数。这是因为在 C++11 中,线程的创建通常涉及到资源的转移,例如线程句柄或执行函数的状态。为了支持这种资源转移,std::thread 的构造函数使用了右值引用,这允许构造函数通过移动语义接管传递给它的资源

具体来说,std::thread 接受一个可调用对象(如函数、函数对象或 lambda 表达式)作为参数,并且这个可调用对象需要是可移动的。以下是构造函数的声明:

template <class Function, class... Args>
explicit thread(Function&& f, Args&&... args);

 在这个构造函数中,Function&& 是一个右值引用,这意味着它可以绑定到一个临时对象或通过 std::move 显式转换成的右值。这允许 std::thread 在构造过程中接管传入的可调用对象的资源,而不需要进行复制。

---------

future与shared_future

future是不可拷贝的。

这意味着一旦 std::future 被创建,它就不能被复制到另一个 std::future 对象中,也不能被添加到需要复制元素的容器(如 std::vectorstd::liststd::deque 等)中。

std::future 支持移动语义。

这意味着它可以被移动到另一个 std::future 对象中,但是一旦移动,原始的 future 对象将不再有效。这使得 std::future 对象不适合在容器中存储或在多个线程间共享。

虽然可以将 std::future 对象存储在需要移动语义的容器(如 std::vector)中,但是每次操作(如添加或插入)都会导致原始 future 对象失效。

std::future 提供了一种访问异步操作结果的方式,但是它被设计为只能由一个线程访问其结果。

std::future 提供的 .get() 和 .wait() 方法只能由一个线程调用一次。如果多个线程尝试访问同一个 std::future 对象的结果,则只有第一个调用这些方法的线程能够成功,其他线程将抛出 std::future_error 异常。

一旦 std::future 的结果被获取,它就会变成一个 “ready” 状态,之后就不能再被获取了。这意味着一旦一个线程获取了结果,其他线程就不能再访问该结果。

---------

shared_future是可以拷贝的。

当需要将future放在容器时,使用shared_future。

当需要在多个线程之间共享 future 的结果时,使用 shared_future

std::shared_future 允许多个线程共享同一个异步操作的结果。

  • shared_future 可以被复制,因此可以在多个线程中安全地共享。
  • shared_future 可以多次调用 get() 方法来获取异步操作的结果,直到所有 shared_future 对象都被销毁。
  • 当所有共享的 shared_future 对象都析构时,关联的 promise 或 packaged_task 也会被析构。

以下是如何使用 shared_future 在容器中存储异步操作结果的示例:

#include <iostream>
#include <future>
#include <vector>int main() {// 创建一个 promise 和一个 shared_futurestd::promise<int> p;std::shared_future<int> f = p.get_future();// 在容器中存储 shared_future 的多个副本std::vector<std::shared_future<int>> futures;futures.push_back(f); // 复制 shared_futurefutures.push_back(f); // 再次复制 shared_future// 在另一个线程中设置 promise 的值std::thread t([&p] {p.set_value(42);});// 在容器中的每个 shared_future 上调用 get()for (auto& fut : futures) {std::cout << "Future result: " << fut.get() << std::endl;}// 等待线程完成t.join();return 0;
}

由于我们使用的是 shared_future,我们可以在容器中的每个 shared_future 对象上调用 get() 方法来获取结果,而不会遇到任何问题。 

使用 shared_future 需要注意的是,如果多个线程同时等待同一个 shared_future 的结果,如果结果变得可用,它们将同时被唤醒,这可能导致竞争条件,特别是如果这些线程接下来执行的操作需要互斥访问某个资源时。因此,在使用 shared_future 时,需要确保适当的同步机制以避免潜在的问题。

---------

可调用对象:
指的是可以在调用表达式中使用的对象.C++ 中的可调用对象包括以下几种类型:
  1. 函数指针:指向函数的指针可以用来调用对应的函数。

  2. 函数对象(Functors):重载了 operator() 的类对象,使得它们可以被当作函数来调用。

  3. Lambda 表达式:C++11 引入的匿名函数,可以作为临时对象使用。

  4. 成员函数指针:指向类成员函数的指针,需要结合对象来调用。

  5. 绑定器和函数适配器:如 std::bind 和 std::function,可以用来包装其他可调用对象,提供额外的灵活性。

示例:

函数指针
void func(int x) {std::cout << x << std::endl;
}int main() {void (*funcPtr)(int) = func; // 声明一个函数指针funcPtr(42); // 通过函数指针调用函数return 0;
}
函数对象(Functor)
struct Functor {void operator()(int x) {std::cout << x << std::endl;}
};int main() {Functor functor;functor(42); // 通过函数对象调用return 0;
}
Lambda 表达式
int main() {auto lambda = [](int x) { std::cout << x << std::endl; };lambda(42); // 通过 lambda 表达式调用return 0;
}
 成员函数指针
class MyClass {
public:void memberFunc(int x) {std::cout << x << std::endl;}
};int main() {MyClass obj;void (MyClass::*memberFuncPtr)(int) = &MyClass::memberFunc;(obj.*memberFuncPtr)(42); // 通过成员函数指针调用return 0;
}
std::function
#include <functional>
#include <iostream>void func(int x) {std::cout << x << std::endl;
}int main() {std::function<void(int)> funcObj = func;funcObj(42); // 通过 std::function 调用auto lambda = [](int x) { std::cout << x << std::endl; };funcObj = lambda;funcObj(24); // 通过 std::function 调用 lambdareturn 0;
}

在 C++ 中,可调用对象的使用非常灵活,能够实现多种编程模式,如回调函数、策略模式等。标准库中的许多算法也接受可调用对象作为参数,例如 std::sortstd::for_each 等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/464067.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python小游戏19——滑雪小游戏

运行效果 python代码 import pygame import random # 初始化Pygame pygame.init() # 设置屏幕尺寸 screen_width 800 screen_height 600 screen pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption("滑雪小游戏") # 定义颜色 WH…

批量删除redis数据【亲测可用】

文章目录 引言I redis客户端基础操作key的命名规则批量查询keyII 批量删除key使用连接工具进行分组shell脚本示例其他方法III 知识扩展:控制短信验证码获取频率引言 批量删除redis数据的应用: 例如缓存数据使用了新的key存储,需要删除废弃的key。RedisTemplate的key序列化采…

04字符串算法/代码随想录

四、字符串 反转字符串 力扣344 遇到数组双指针真是太好用了&#xff0c;左右指针不断逼近即可&#xff0c;代码也很简单 class Solution {public void reverseString(char[] s) {int fast s.length - 1;int slow 0;while (slow < fast) {char temp s[fast];s[fast] s[…

conda找不到对应版本的pytorch,就会自动下载cpu版本的

踩坑一&#xff1a; conda install pytorch2.0.1 torchvision0.15.2 torchaudio2.0.2 pytorch-cuda11.7 -c pytorch -c nvidia (本人的服务器支持的 且python3.8.20) 先nvidia-smi查看自己cuda支持的最高版本&#xff0c;然后去pytorch官网寻找对应的torch、torchaudio、to…

信息学科平台设计与实现:Spring Boot技术详解

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

二、应用层,《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》

文章目录 零、前言一、应用层协议原理1.1 网络应用的体系结构1.1.1 客户-服务器(C/S)体系结构1.1.2 对等体&#xff08;P2P&#xff09;体系结构1.1.3 C/S 和 P2P体系结构的混合体 1.2 进程通信1.2.1 问题1&#xff1a;对进程进行编址&#xff08;addressing&#xff09;&#…

Java面向对象 C语言字符串常量

1. &#xff08;1&#xff09;. package liujiawei;public class Phone {String brand;double price;public void call(){System.out.println("手机打电话");}public void play(){System.out.println("手机打游戏");} } public class phonetest {public…

【逆向基础】十八、PE文件格式(三)

一、简介 文本章主要讲结构体IMAGE_DATA_DIRECTORY数组。它制定了各种数据目录的地址与大&#xff1b;PE装载器可以通过这些信息准确加载PE文件所需的函数&#xff0c;资源等&#xff1b;此外&#xff0c;数据目录表也是设置钩子&#xff0c;注入等逆向的理论基础。所以学习这…

Session条件竞争--理论

条件竞争 多个线程同时访问一个共享变量或文件时&#xff0c;由于线程的执行顺序不符合预期而导致最后的执行结果不符合开发者的预期。 session session,被称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样&#xff0c;当用户在应用程序的Web页之间…

Centos8安装软件失败更换镜像源

问题 在Centos 8上安装git&#xff0c;报错如下&#xff1a; sudo dnf install git -y Repository extras is listed more than once in the configuration CentOS Linux 8 - AppStream 0.0 B/s …

如何让网页中的图片不可下载,让文字不可选中/复制

使用css中的伪属性来完成这个操作. 效果展示 文字不可复制: 图中这几个中文字符无法被选中,双击前面这几个字也只能选中后面的英文内容,无法选中也就无法复制. 既然常规方式无法选中,那打开浏览器开发者工具总能复制吧! 我经常这样干, 但是很遗憾,页面检查中根本就没那些内容…

Linux 之 信号概念、进程、进程间通信、线程、线程同步

学习任务&#xff1a; 1、 信号&#xff1a;信号的分类、进程对信号的处理、向进程发送信号、信号掩码 2、 进程&#xff1a;进程与程序的概念、进程的内存布局、进程的虚拟地址空间、fork创建子进程、wait监视子进程 3、 学习进程间通信&#xff08;管道和FIFO、信号、消息队列…

Jmeter——结合Allure展示测试报告

在平时用jmeter做测试时&#xff0c;生成报告的模板&#xff0c;不是特别好。大家应该也知道allure报告&#xff0c;页面美观。 先来看效果图&#xff0c;报告首页&#xff0c;如下所示&#xff1a; 报告详情信息&#xff0c;如下所示&#xff1a; 运行run.py文件&#xff0c;…

ElasticSearch - Bucket Script 使用指南

文章目录 官方文档Bucket Script 官文1. 什么是 ElasticSearch 中的 Bucket Script&#xff1f;2. 适用场景3. Bucket Script 的基本结构4. 关键参数详解5. 示例官方示例&#xff1a;计算每月 T 恤销售额占总销售额的比率百分比示例计算&#xff1a;点击率 (CTR) 6. 注意事项与…

java、excel表格合并、指定单元格查找、合并文件夹

#创作灵感# 公司需求 记录工作内容 后端&#xff1a;JAVA、Solon、easyExcel、FastJson2 前端&#xff1a;vue2.js、js、HTML 模式1&#xff1a;合并文件夹 * 现有很多文件夹 想合并全部全部的文件夹的文件到一个文件夹内 * 每个部门发布的表格 合并全部的表格为方便操作 模…

【初阶数据结构篇】链式结构二叉树(二叉链)的实现(感受递归暴力美学)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

aws(学习笔记第十课) 对AWS的EBS如何备份(snapshot)以及使用snapshot恢复数据,AWS实例存储

aws(学习笔记第十课) 对AWS的EBS如何备份&#xff08;snapshot&#xff09;以及使用snapshot&#xff0c;AWS实例存储 学习内容&#xff1a; 对AWS的EBS如何备份AWS实例存储EBS和实例存储的不足 1. 对AWS的EBS如何备份&#xff08;snapshot&#xff09;以及使用snapshot恢复数…

适用于 c++ 的 wxWidgets框架源码编译SDK-windows篇

本文章记录了下载wxWidgets源码在windows 11上使用visual Studio 2022编译的全过程,讲的不详细请给我留言,让我知道错误并改进。 本教程是入门级。有更深入的交流可以留言给我。 如今互联网流行现在大家都忘记了这块桌面的开发,我认为桌面应用还是有用武之地,是WEB无法替代…

Pycharm贪吃蛇小游戏后续2

前文中我们提到用面向对象去编写贪吃蛇 目前功能实现贪吃蛇吃食物&#xff0c;身体加长&#xff0c;其次可以按下-&#xff08;键盘上右分大小写的-&#xff0c;不是数字的-&#xff09;来改变speed&#xff0c;终端可以看到速度&#xff0c;后续将陆续实现撞墙死亡&#xff0…

你丢失的数据,10款数据恢复软件帮你找!!

现实与虚拟的交错&#xff0c;互联网的进步&#xff0c;加大了我们之间交流的效率&#xff0c;而且便便捷了许许多多的事&#xff0c;比如信息保存&#xff1b;今天咱们来聊聊数据恢复这个话题。你是不是会一不小心删除了重要文件&#xff1f;硬盘出了问题&#xff0c;数据不见…