Добавить класс друга после объявления - PullRequest
3 голосов
/ 13 ноября 2011

Я пытаюсь написать сервер именованных каналов на C ++. У меня есть класс с именем client_pool, который содержит контейнер экземпляров канала и одну открытую функцию-член write, которая асинхронно отправляет данные всем подключенным клиентам.

Проблема в том, что клиенты имеют тенденцию неожиданно отключаться. Когда это происходит, вызов на WriteFileEx завершается неудачно с ERROR_NO_DATA. Когда это происходит, я хочу перейти к классу client_pool и сказать ему, чтобы он закрыл дескриптор клиента и удалил его из контейнера. Однако, поскольку WriteFileEx очень сложно использовать, я создал вспомогательный класс с именем write_context в анонимном пространстве имен.

Итак, в результате я хочу вызвать закрытый метод в client_pool, который объявлен в clients.h, из класса write_context, который объявлен в clients.cpp. Нечто подобное (детали / обработка ошибок опущены):

clients.h

class client_pool {
    struct implementation;
    std::unique_ptr<implementation> pimpl;
public:
    void write(uint8_t *data, size_t size);
};

clients.cpp

struct client_pool::implementation {
    set<HANDLE> connected;
    // ...
    void disconnect(HANDLE victim)
    {
        CloseHandle(victim);
        connected.erase(victim);
    }
};

namespace { struct write_context {
    OVERLAPPED overlapped;
    client_pool *owner;
    HANDLE target;
    const uint8_t *buffer;
    size_t total_size;
    size_t written;
    // ...
    void next_chunk()
    {
        if(!WriteFileEx(/* ... */, write_context::completion_routine)) {
            if(GetLastError() == ERROR_NO_DATA) {
                // I want to do something like
                owner->pimpl->disconnect(target);
            }
        }
    }
    static void CALLBACK completion_routine(DWORD errcode, DWORD transferred, LPOVERLAPPED overlapped)
    {
        auto self = reinterpret_cast<write_context*>(overlapped);
        self->written += transferred;
        if(errcode == ERROR_MORE_DATA) {
            self->next_chunk();
        } else {
            delete self;
        }
    }
}; }

void client_pool::write(uint8_t *data, size_t size)
{
    for each handle in pimpl->connected {
        auto context = new write_context(this, handle, data, size);
        context->next_chunk();
    }
}

Очевидно, строка owner->pimpl->disconnect(target); не компилируется, потому что pimpl является приватной. Что я могу сделать / каковы мои заместители?

Ответы [ 4 ]

2 голосов
/ 13 ноября 2011

Прямой доступ к pimpl-> connected и write_context непосредственно в вашем методе client_pool :: write в некотором роде противоречит точке зрения на pimpl. В противном случае вы могли бы привести доводы до тех пор, пока не столкнетесь с такой проблемой.

Я бы просто создал метод реализации :: записи, для которого вы можете передавать аргументы и указатель на client_pool.

1 голос
/ 13 ноября 2011

Сделать write_context другом implementation.Передайте pimpl как owner из write_context.

1 голос
/ 13 ноября 2011

Я думаю, что если вы используете именованное пространство имен вместо анонимного пространства имен, вы можете поместить эту строку в определение класса:

friend void namespace_name::next_chunk()

Или же все эти дополнительные вещи помещаются в пространство анонимных имен как статические функции внутри класса. Поскольку статические методы и структуры не изменяют ABI, вы можете скрыть его от всех других экземпляров с помощью трюка препроцессора.

Или есть жестокие и ужасающие:

#define class struct
#define private public
#define protected public
0 голосов
/ 13 ноября 2011

Извините, но это не лучший способ использовать PIMPL.

PIMPL скрывает детали реализации своего владельца и должен быть доступен только через интерфейс его владельцев. Итак, если вы хотите вызвать метод «client_pool :: creation», его следует переместить в интерфейс «client_pool», а его реализация должна делегировать работу классу «client_pool :: creation». В другом случае это выглядит как ошибка дизайна.

...