在循环引用的上下文中处理循环

时间:2013-01-22 11:20:22

标签: c++ include client-server circular-dependency circular-reference

作为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;
}

3 个答案:

答案 0 :(得分:4)

这对我来说很好看。在client.h中转发声明服务器并在server.h中转发声明客户端是正确的做法。

然后在.c或.cpp文件中包含两个头文件是完全没问题的 - 您需要避免的是将头文件包含在一个圆圈中。

答案 1 :(得分:3)

  

上述“黑客”听起来合理吗?我应该期待一些吗?   不可预见的后果?没有任何“聪明”的方法可以做到这一点   必须实现代理类?

转发声明并使用include directive是打破循环包含的正常和正确方法。

答案 2 :(得分:3)

“Hack”是没有的,像你一样将两个类的声明和实现分开是很常见的做法。 *.cpp包括两个标题是完全正常的。


旁注:首先考虑setServersetClient方法的不同签名:在这两种方法中,复制参数。这两个副本都是非常重要的,因为必须更新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_ptrServer的析构函数   未在client.h内声明。在相当高的警告水平   你编译器应该抱怨在未定义上调用delete   类'指针。