作为an older question of mine的后续,我希望实现客户端 - 服务器模型模拟,其中客户端启动一系列涉及在服务器上调用方法的操作,而这些方法又可以调用方法客户端(让我们忽略堆栈可能爆炸的问题)。
更具体地说,由于我想从定义中拆分实现,因此我将为Server类提供server.h和server.cpp,为Client类提供client.h和client.cpp。由于Server拥有对Client的引用并从中调用方法,因此需要#include "client.h"
。此外,客户端持有对服务器的引用并从中调用方法,它需要#include "server.h"
。此时,即使我在server.h和client.h中都使用了头部保护,它仍然会混乱(是的,这是预期的)所以我决定在client.h中向前声明Server类,在服务器中向前传递Client类。 。H。不幸的是,这还不足以解决这个问题,因为我也在调用这两个类中的方法,所以我设法将它编译成&运行(正如我所知道的那样),在client.cpp中包含server.h,在server.cpp中包含client.h。
上述“黑客”听起来合理吗?我应该期待一些不可预见的后果吗?在没有实现代理类的情况下,是否有任何“更聪明”的方法可以做到这一点?
以下是实施方式的基本示例:
file 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
file 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;
}
file 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
file 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 :(得分:4)
这对我来说很好看。在client.h中转发声明服务器并在server.h中转发声明客户端是正确的做法。
然后在.c或.cpp文件中包含两个头文件是完全没问题的 - 您需要避免的是将头文件包含在一个圆圈中。
答案 1 :(得分:3)
上述“黑客”听起来合理吗?我应该期待一些吗? 不可预见的后果?没有任何“聪明”的方法可以做到这一点 必须实现代理类?
转发声明并使用include directive
是打破循环包含的正常和正确方法。
答案 2 :(得分:3)
“Hack”是没有的,像你一样将两个类的声明和实现分开是很常见的做法。 *.cpp
包括两个标题是完全正常的。
旁注:首先考虑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 类'指针。