Работа с циркулярными включает в контексте циркулярных ссылок

Как продолжение до мой старый вопрос, Я хочу реализовать симуляцию клиент-сервер, где клиент инициирует последовательность действий, которые включают вызовы методов на сервере, которые, в свою очередь, могут вызывать методы на клиенте (давайте проигнорируем проблему, которую может взорвать стек) ,

Более конкретно, поскольку я хочу отделить реализацию от определения, у меня будут server.h и server.cpp для класса Server и client.h и client.cpp для класса Client. Поскольку сервер содержит ссылку на клиента и вызывает из него методы, он должен #include "client.h", Кроме того, Клиент хранит ссылку на Сервер и вызывает с него методы, ему необходимо #include "server.h", На этом этапе, даже если я использую средства защиты заголовков как в server.h, так и в client.h, он все еще не работает (да, это ожидаемо), поэтому я решил объявить переадресацию класса Server в client.h и класса Client в server .час. К сожалению, этого недостаточно для решения проблемы, потому что я также вызываю методы из двух классов, поэтому мне удалось заставить его скомпилировать & запустить (правильно, насколько я могу судить), включив server.h в client.cpp и client.h в server.cpp.

Вышеупомянутое «взломать» звучит разумно? Стоит ли ожидать каких-то непредвиденных последствий? Есть ли «умнее» способ сделать это без необходимости реализации прокси-класса?

Вот элементарный пример того, как будет выглядеть реализация:

файл client.h:

#ifndef CLIENT_H
#define CLIENT_H

#include <iostream>
#include <memory>

class Server;

class Client
{
private:
std::shared_ptr<const Server> server;

public:
Client () {}

void setServer (const std::shared_ptr<const Server> &server);

void doStuff () const;

void doOtherStuff () const;
};

#endif

файл client.cpp:

#include "client.h"#include "server.h"
void Client::setServer (const std::shared_ptr<const Server> &server)
{
this->server = server;
}

void Client::doStuff () const
{
this->server->doStuff();
}

void Client::doOtherStuff () const
{
std::cout << "All done!" << std::endl;
}

файл server.h:

#ifndef SERVER_H
#define SERVER_H

#include <iostream>
#include <memory>

class Client;

class Server
{
private:
std::weak_ptr<const Client> client;

public:
Server () {}

void setClient (const std::weak_ptr<const Client> &client);

void doStuff () const;
};

#endif

файл sever.cpp:

#include "server.h"#include "client.h"
void Server::setClient (const std::weak_ptr<const Client> &client)
{
this->client = client;
}

void Server::doStuff () const
{
this->client.lock()->doOtherStuff();
}

файл main.cpp:

#include <iostream>
#include <memory>

#include "client.h"#include "server.h"
int main ()
{
std::shared_ptr<Client> client(new Client);
std::shared_ptr<Server> server(new Server);

client->setServer(server);
server->setClient(client);

client->doStuff();

return 0;
}

0

Решение

«Взломать» — это ничто, совершенно обычная практика — разделять объявление и реализацию двух классов, как вы это делали. И это совершенно нормально, что *.cpp включите оба заголовка.


Sidenote: Сначала рассмотрите различные подписи для вашего setServer а также setClient методы: в обоих методах вы копируете аргумент. Обе копии являются нетривиальными, так как use_counts и / или weak_count должны быть обновлены. Если аргумент действительно является существующим аргументом, это нормально, но если он является временным, копия будет увеличивать количество, а уничтожение временного будет уменьшать его снова, каждый раз, когда необходимо разыменовать внутренний указатель. По сравнению, перемещение shared_ptr или weak_ptr не влияет на количество использований, но сбрасывает временное значение. Уничтожение этого временного сброса снова не влияет на счетчик использования (фактически это нулевой указатель).
Во-вторых, всегда предпочитаю make_shared более простой new, потому что это экономит вам одно выделение. Так что используйте эту реализацию вместо:

void Client::setServer (std::shared_ptr<const Server> server)
{
this->server = std::move(server);
}

int main ()
{
auto client = std::make_shared<Client>(); //prefer make_shared
auto server = std::make_shared<Server>();
/* 1 */
client->setServer(server); //by copy, if you need to continue to use server
/* 2 */
server->setClient(std::move(client)); //by moving
}

Звонок 1 будет таким же дорогим, вы сделаете одну копию shared_ptr, только в этот раз вы делаете это при передаче аргумента, а не внутри метода. Звонок 2 будет дешевле, потому что shared_ptr перемещается и никогда не копируется.


Мое следующее утверждение неверно (см. Комментарии) и относится только к unique_ptrне shared_ptr:

Но: так как вы используете std::shared_ptr<const Server> в Client, вы
придется определить Clientдеструктор внутри client.cpp,
причина в том, что если вы этого не сделаете, компилятор сгенерирует его для вас,
вызывая shared_ptrи так Serverдеструктор который имеет
не был объявлен внутри client.h, На достаточно высоких уровнях предупреждения
ваш компилятор должен пожаловаться на вызов delete на неопределенный
указатель класса.

3

Другие решения

Это выглядит хорошо для меня. Переадресация объявления сервера в client.h и пересылка объявления клиента в server.h — правильная вещь.

Вполне возможно, что оба файла заголовков будут включены в файл .c или .cpp — все, что вам нужно избегать, это включать файлы заголовков в кружок.

4

Вышеупомянутое «взломать» звучит разумно? Должен ли я ожидать некоторые
непредвиденные последствия? Есть ли «умнее» способ сделать это без
необходимость реализовать прокси-класс?

Предварительная декларация и использование include directive это нормальный и правильный способ разорвать круговой включить.

3
По вопросам рекламы [email protected]