0

作为我的一个较老问题的后续,我希望实现一个客户端-服务器模型模拟,其中客户端启动一系列操作,这些操作涉及调用服务器上的方法,而服务器又可以调用客户端上的方法(让我们忽略堆栈可能会爆炸的问题)。

更具体地说,由于我想将实现从定义中分离出来,我将为 Server 类提供 server.h 和 server.cpp,为 Client 类提供 client.h 和 client.cpp。由于 Server 持有对 Client 的引用并从中调用方法,因此它需要#include "client.h". 此外,Client 持有对 Server 的引用并从中调用方法,它需要#include "server.h". 此时,即使我在 server.h 和 client.h 中都使用了标头保护,它仍然会搞砸(是的,这是意料之中的)所以我决定在 client.h 中转发声明 Server 类,在 server 中声明 Client 类。H。不幸的是,这还不足以解决问题,因为我也在调用这两个类的方法,所以我设法通过在客户端中包含 server.h 来使其编译和运行(据我所知是正确的) server.cpp 中的 .cpp 和 client.h。

上面的“hack”听起来合理吗?我应该期待一些无法预料的后果吗?是否有任何“更智能”的方法可以做到这一点而无需实现代理类?

这是实现的基本示例:

文件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

文件 server.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;
}
4

3 回答 3

4

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

然后将两个头文件都包含在 .c 或 .cpp 文件中是非常好的 - 您需要避免的只是将头文件包含在一个圆圈中。

于 2013-01-22T11:31:24.937 回答
3

上面的“hack”听起来合理吗?我应该期待一些无法预料的后果吗?是否有任何“更智能”的方法可以做到这一点而无需实现代理类?

前向声明和使用include directiveto 是打破循环包含的正常和正确的方法。

于 2013-01-22T11:31:22.437 回答
3

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


旁注:首先考虑您的setServersetClient方法的不同签名:在这两种方法中,您都复制了参数。两个副本都很重要,因为必须更新 use_counts 和/或 weak_count。如果参数确实是现有参数,那没关系,但如果它是临时的,则副本将增加计数,而临时的销毁将再次减少它,每次必须取消引用内部指针。相反,移动shared_ptr 或 weak_ptr 不会影响使用计数,但会重置临时值。再次破坏该重置临时不会影响使用计数(它实际上是一个空指针)。其次,总是更喜欢make_sharedsimple 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 将与以前一样昂贵,您制作一个副本 pf the shared_ptr,只是这次您在传递参数时制作它,而不是在方法内部。调用 2 会更便宜,因为它shared_ptr被移动并且从未被复制。


我的以下陈述是错误的(见评论),仅适用于unique_ptr,不适用于shared_ptr

但是:因为你使用了std::shared_ptr<const Server>in Client,你必须在Client里面定义 ' 的析构函数client.cpp。原因是如果你不这样做,编译器会为你生成它,调用shared_ptr's 和因此Server's 的析构函数,它没有在里面声明client.h。在相当高的警告级别,您的编译器应该抱怨在未定义的类指针上调用 delete。

于 2013-01-22T11:38:37.350 回答