Можно ли использовать smart ptr или boost intrusive ptr в качестве значения «void * tag» на сервере gRP C asyn c, написанном на C ++ - PullRequest
1 голос
/ 08 мая 2020

Я пишу asyn c gRP C сервер на C ++ (на windows). Я хотел бы использовать тип назначаемого указателя boost для значения 'tag' - указатель на объекты-обработчики RP C, которые возвращаются во время метода Next () очереди завершения.

gRP Служба C asyn c требует передачи void * объекту обработчика, чтобы он мог вызвать обработчик при возникновении связанного события. Проблема в том, что я не могу найти способ преобразовать мой назойливый указатель boost в void * таким образом, чтобы сохранить и использовать счетчик ссылок.

Возможно ли это? Или это будет работать только в том случае, если метод, которому я передаю указатель, ожидает указатель ускорения?

1 Ответ

0 голосов
/ 09 мая 2020

Допустим, у нас есть сторонняя библиотека, которая принимает 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();
}
...