0

私の以前の質問のフォローアップとして、クライアントサーバーモックアップシミュレーションを実装したいと思います。このシミュレーションでは、クライアントがサーバー上のメソッドの呼び出しを含む一連のアクションを開始し、サーバー上のメソッドを呼び出すことができます(スタックが爆発する可能性があるという問題は無視しましょう)。

具体的には、実装を定義から分割したいので、Serverクラスにはserver.hとserver.cppを、Clientクラスにはclient.hとclient.cppを用意します。サーバーはクライアントへの参照を保持し、そこからメソッドを呼び出すため、を行う必要があり#include "client.h"ます。また、クライアントはサーバーへの参照を保持し、そこからメソッドを呼び出す必要があります#include "server.h"。この時点で、server.hとclient.hの両方でヘッダーガードを使用しても、それはまだ混乱しているので(ええ、それは予想されます)、client.hのServerクラスとserverのClientクラスを前方宣言することにしました。 .h。残念ながら、これでは問題を解決するのに十分ではありません。2つのクラスからメソッドも呼び出しているため、クライアントにserver.hを含めることで、コンパイルして実行することができました(私が知る限り)。 server.cppの.cppとclient.h。

上記の「ハック」は合理的に聞こえますか?予期しない結果を予期する必要がありますか?プロキシクラスを実装せずにこれを行う「よりスマートな」方法はありますか?

実装がどのように見えるかの基本的なサンプルを次に示します。

ファイル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;
}
4

3 に答える 3

4

それは私には良さそうです。client.hでサーバーを転送宣言し、server.hでクライアントを転送宣言するのが正しい方法です。

次に、両方のヘッダーファイルを.cまたは.cppファイルにインクルードすることはまったく問題ありません。回避する必要があるのは、ヘッダーファイルをサークルにインクルードすることだけです。

于 2013-01-22T11:31:24.937 に答える
3

上記の「ハック」は合理的に聞こえますか?予期しない結果を予期する必要がありますか?プロキシクラスを実装せずにこれを行う「よりスマートな」方法はありますか?

前方宣言とinclude directivetoの使用は、循環インクルードを破る通常の正しい方法です。

于 2013-01-22T11:31:22.437 に答える
3

「ハック」はありません.2つのクラスの宣言と実装を分離することは完全に一般的な方法です。そして*.cpp、両方のヘッダーが含まれているのは完全に正常です。


setServer補足: まず、とメソッドの異なるシグネチャを検討してくださいsetClient。どちらのメソッドでも、引数をコピーします。use_counts および/または weak_count を更新する必要があるため、両方のコピーは自明ではありません。引数が実際に既存の引数である場合は問題ありませんが、一時的なものである場合は、内部ポインターを逆参照する必要があるたびに、コピーによってカウントが増加し、一時的なものを破棄すると再びカウントが減少します。対照的に、shared_ptr または weak_ptr を移動しても、使用回数には影響しませんが、一時はリセットされます。その一時的なリセットを再度破棄しても、使用回数には影響しません (実質的に null ポインターです)。次に、1 つの割り当てを節約できるため、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
}

Call 1 は以前と同じようにコストがかかります。 のコピーを 1 つ作成しますが、shared_ptr今回はメソッド内ではなく、引数を渡しながら作成します。コール 2shared_ptrは移動され、コピーされることはないため、コストが低くなります。


私の次のステートメントは誤りです (コメントを参照unique_ptr) shared_ptr

ただし: std::shared_ptr<const Server>inを使用するため、のデストラクタを insideClientで定義する必要があります。その理由は、そうしないと、コンパイラがそれを生成し、の内部で宣言されていない のデストラクタを呼び出すためです。適度に高い警告レベルでは、コンパイラは、未定義のクラスのポインターで delete を呼び出すことについて文句を言うべきです。Clientclient.cppshared_ptrServerclient.h

于 2013-01-22T11:38:37.350 に答える