Допустим, у нас есть сторонняя библиотека, которая принимает stati c функции обратного вызова, которые могут использовать void*
пользовательские данные для передачи определенного пользователем состояния вокруг:
namespace SomeAPI {
typedef void(*Callback)(int, void* userdata);
struct Registration;
Registration const* register_callback(Callback cb, void* userdata);
size_t deregister_callback(Callback cb, void* userdata);
void some_operation_invoking_callbacks();
}
Минималистичная реализация этого поддельный API, например:
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
return std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
Let's Have A Client
Клиент владеет состоянием, которым управляет некоторый общий указатель:
struct MyClient {
struct State {
std::string greeting;
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
Теперь нам нужен Член State::foo
должен быть зарегистрирован как обратный вызов, а state_
должен быть передан как пользовательские данные:
MyClient(std::string g) : state_(std::make_shared<State>(State{g})) {
SomeAPI::register_callback(static_handler, &state_);
}
~MyClient() noexcept {
SomeAPI::deregister_callback(static_handler, &state_);
}
static void static_handler(int i, void* userdata) {
auto& state = *static_cast<SharedState*>(userdata);
state->foo(i);
}
};
Теперь немного поработаем с клиентом:
Live On Coliru
int main() {
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
Печать:
------- operation start
State::foo with i:1 and greeting:"Foo"
------- operation start
State::foo with i:2 and greeting:"Foo"
State::foo with i:3 and greeting:"Bar"
------- operation start
State::foo with i:4 and greeting:"Foo"
Tight-Rope Act
Если вы на самом деле хочет передать право собственности на API, в том смысле, что он сохраняет состояние, даже если экземпляр Client
ушел, вы, по определению, вызовете утечку этого состояния.
Единственное решение для этого было бы, если бы у API был какой-то обратный вызов, чтобы сигнализировать, что он должен очистить. Я никогда не видел такого дизайна API, но вот как он будет выглядеть:
enum { TEARDOWN_MAGIC_VALUE = -1 };
void InitAPI() {}
void ShutdownAPI() {
for (auto it = s_callbacks.begin(); it != s_callbacks.end();) {
it->cb(TEARDOWN_MAGIC_VALUE, it->userdata);
it = s_callbacks.erase(it);
}
}
Теперь мы можем передать динамически выделенную копию shared_ptr
в обратный вызов как пользователь -data (вместо необработанного указателя на принадлежащую копию общего указателя):
SomeAPI::register_callback(static_handler, new SharedState(state_));
Обратите внимание, что, поскольку SomeAPI
теперь имеет копию общий указатель, счетчик ссылок увеличился. Фактически, нам больше не нужно отменять регистрацию обратного вызова, потому что состояние будет оставаться в силе до тех пор, пока SomeAPI
не отключится:
static void static_handler(int i, void* userdata) {
auto* sharedstate = static_cast<SharedState*>(userdata);
if (i == SomeAPI::TEARDOWN_MAGIC_VALUE) {
delete sharedstate; // decreases refcount
return;
} else {
(*sharedstate)->foo(i);
}
}
Основная программа в основном не изменилась, но для InitAPI()
и ShutdownAPI()
вызывает:
int main() {
SomeAPI::InitAPI();
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
SomeAPI::ShutdownAPI();
}
Путем умной трассировки деструкторов вы можете убедиться, что время жизни State
теперь фактически управляется / разделяется ShutdownAPI
:
Live On Coliru
------- operation start
State::foo with i:1 and greeting:"Foo"
------- operation start
State::foo with i:2 and greeting:"Foo"
State::foo with i:3 and greeting:"Bar"
~MyClient (Bar)
------- operation start
State::foo with i:4 and greeting:"Foo"
State::foo with i:5 and greeting:"Bar"
~State (Bar)
~MyClient (Foo)
~State (Foo)
Полный список (Classi c)
Live On Coliru
#include <memory>
#include <list>
#include <string>
#include <iostream>
#include <iomanip>
namespace SomeAPI {
using Callback = void(*)(int, void* userdata);
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
return std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
}
struct MyClient {
struct State {
std::string greeting;
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
MyClient(std::string g) : state_(std::make_shared<State>(State{g})) {
SomeAPI::register_callback(static_handler, &state_);
}
~MyClient() noexcept {
SomeAPI::deregister_callback(static_handler, &state_);
}
static void static_handler(int i, void* userdata) {
auto& state = *static_cast<SharedState*>(userdata);
state->foo(i);
}
};
int main() {
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
Полный листинг (придуманный)
Live On Coliru
#include <memory>
#include <list>
#include <string>
#include <iostream>
#include <iomanip>
namespace SomeAPI {
enum { TEARDOWN_MAGIC_VALUE = -1 };
using Callback = void(*)(int, void* userdata);
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
bool const matched = std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
if (matched) {
r.cb(TEARDOWN_MAGIC_VALUE, r.userdata);
}
return matched;
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
void InitAPI() {}
void ShutdownAPI() {
for (auto it = s_callbacks.begin(); it != s_callbacks.end();) {
it->cb(TEARDOWN_MAGIC_VALUE, it->userdata);
it = s_callbacks.erase(it);
}
}
}
struct MyClient {
struct State {
std::string greeting;
State(std::string g) : greeting(std::move(g)) {}
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
~State() noexcept {
std::cout << "~State (" << greeting << ")\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
MyClient(std::string g) : state_(std::make_shared<State>(std::move(g))) {
SomeAPI::register_callback(static_handler, new SharedState(state_));
}
~MyClient() {
std::cout << "~MyClient (" << state_->greeting << ")\n";
}
static void static_handler(int i, void* userdata) {
auto* sharedstate = static_cast<SharedState*>(userdata);
if (i == SomeAPI::TEARDOWN_MAGIC_VALUE) {
delete sharedstate; // decreases refcount
return;
} else {
(*sharedstate)->foo(i);
}
}
};
int main() {
SomeAPI::InitAPI();
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
SomeAPI::ShutdownAPI();
}