Member function pointers can be used with asynchronous operations, but require careful handling of object lifetime and thread safety.
Here’s a basic example using std::async
:
#include <future>
#include <iostream>
#include <thread>
class Character {
public:
int CalculateDamage(int BaseAmount) {
// Simulate complex calculation
std::this_thread::sleep_for(
std::chrono::seconds(1));
return BaseAmount * 2;
}
};
int main() {
Character player;
// Launch async calculation
auto future =
std::async(std::launch::async,
&Character::CalculateDamage,
&player, 100);
std::cout << "Calculating...\n";
int result = future.get();
std::cout << "Damage: " << result << '\n';
}
Calculating...
Damage: 200
Here's a more complex example using a thread pool:
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
class ThreadPool {
public:
ThreadPool(size_t NumThreads) {
for (size_t i = 0; i < NumThreads; ++i) {
workers.emplace_back([this]{
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(
queue_mutex);
condition.wait(lock, [this]{
return stop || !tasks.empty();
});
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
// New generic version that works with any callable
template <typename F, typename... Args>
auto EnqueueTask(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<
F, Args...>::type> {
using return_type = typename
std::invoke_result<F, Args...>::type;
auto task = std::make_shared<
std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f),
std::forward<Args>(args)...));
std::future<return_type> future = task->
get_future();
{
std::unique_lock<std::mutex> lock(
queue_mutex);
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return future;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(
queue_mutex);
stop = true;
}
condition.notify_all();
for (auto& worker : workers) {
worker.join();
}
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop{false};
};
class Character {
public:
void ProcessAction(
const std::string& Action) {
std::cout << "Processing " << Action <<
" on thread "
<< std::this_thread::get_id() << '\n';
std::this_thread::sleep_for(
std::chrono::milliseconds(500));
}
};
int main() {
ThreadPool pool(2);
Character player;
// Using member function
auto f1 = pool.EnqueueTask(
&Character::ProcessAction, &player,
"Attack");
auto f2 = pool.EnqueueTask(
&Character::ProcessAction, &player,
"Defend");
f1.wait();
f2.wait();
return 0;
}
Processing Attack on thread 32816
Processing Defend on thread 30300
Key considerations when using member function pointers asynchronously:
The thread pool approach is particularly useful for game engines where you want to process multiple character actions concurrently while managing thread resources efficiently.
Answers to questions are automatically generated and may not have been reviewed.
Learn how to create pointers to class functions and data members, and how to use them