Как продолжение до мой старый вопрос, Я хочу реализовать симуляцию клиент-сервер, где клиент инициирует последовательность действий, которые включают вызовы методов на сервере, которые, в свою очередь, могут вызывать методы на клиенте (давайте проигнорируем проблему, которую может взорвать стек) ,
Более конкретно, поскольку я хочу отделить реализацию от определения, у меня будут 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;
}
«Взломать» — это ничто, совершенно обычная практика — разделять объявление и реализацию двух классов, как вы это делали. И это совершенно нормально, что *.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 на неопределенный
указатель класса.
Это выглядит хорошо для меня. Переадресация объявления сервера в client.h и пересылка объявления клиента в server.h — правильная вещь.
Вполне возможно, что оба файла заголовков будут включены в файл .c или .cpp — все, что вам нужно избегать, это включать файлы заголовков в кружок.
Вышеупомянутое «взломать» звучит разумно? Должен ли я ожидать некоторые
непредвиденные последствия? Есть ли «умнее» способ сделать это без
необходимость реализовать прокси-класс?
Предварительная декларация и использование include directive
это нормальный и правильный способ разорвать круговой включить.