0

作爲an older question of mine的後續,我希望實現客戶端 - 服務器模型仿真,其中客戶端啓動一系列涉及調用服務器上方法的操作,其中轉,可以調用客戶端上的方法(讓我們忽略堆棧可能炸燬的問題)。處理通知包括循環引用

更具體地說,因爲我想從定義中分離出實現,所以我將爲Server類使用server.h和server.cpp,爲Client類使用client.h和client.cpp。由於服務器持有對客戶端的引用並從中調用方法,因此它需要#include "client.h"。此外,客戶端持有對服務器的引用並從中調用方法,它需要#include "server.h"。在這一點上,即使我在server.h和client.h中都使用了標頭守護程序,它仍然會出現錯誤(是的,這是預期的),所以我決定在client.h中轉發聲明Server類,並在服務器中聲明Client類。H。不幸的是,這還不足以解決問題,因爲我也在調用這兩個類的方法,所以我設法通過在客戶端中包含server.h來編譯& run(正確地說,據我所知)。 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; 
} 
+1

對我來說很正常,不會稱之爲破解。 – Kimi

+0

這通常是如何完成的。 –

回答

3

「黑客」沒有,像你一樣分開聲明和實現兩個類是完全通用的做法。 *.cpp包含兩個標頭是很正常的。


旁註:首先考慮不同的簽名爲您setServersetClient方法:在這兩種方法中,您複製的說法。這兩個副本都是不平凡的,因爲use_counts和/或weak_count必須更新。如果參數確實是一個現有的參數,那沒問題,但如果它是一個臨時參數,則副本會增加計數並且臨時的銷燬會再次減少它,每次內部指針必須被解除引用時。相反,移動 shared_ptr或weak_ptr不影響使用計數,但會重置臨時值。再次破壞臨時重置並不影響使用次數(它實際上是一個空指針)。 其次,總是比new更喜歡make_shared,因爲它爲您節省了一次分配。因此改爲使用此實現:

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的shared_ptr,只有這個時候你讓它同時傳遞參數,而不是方法中。呼叫2將更便宜,因爲shared_ptr四處移動,從不復制。


我下面的語句是假的(見註釋),只適用於unique_ptr,不shared_ptr

但是:由於您使用的Client一個std::shared_ptr<const Server>,你 必須定義Client「在client.cpp裏面的破壞者。 的原因是,如果你不這樣做,編譯器會爲你生成, 調用shared_ptr's,因此Server的析構函數 沒有在client.h內部聲明。在合理高的警告水平 你的編譯器應該抱怨在一個未定義的 類的指針上調用delete。

+3

不,shared_ptr的析構函數是通過在指針附加到共享指針時創建的deleter對象執行的,並且通過虛擬調用調用,因此它實際上並不試圖刪除指針。無論如何,這就是發生的事情,我相信它也會以std的形式發生。 – CashCow

+0

@Gorpik不,你的編輯是錯的,Arne意味着〜Client()應該在頭部聲明,但是在.cpp中定義。然而,這並不需要因爲我解釋的原因。 – CashCow

+0

@CashCow它花了我幾次讀,但我終於明白了。我必須有一個緩慢的一天。阿恩,對不起,編輯錯誤。 – Gorpik

3

請問上面的 「黑客」 聽起來合理嗎?我應該期望一些 意想不到的後果?如果沒有 必須實現一個代理類,有沒有什麼「更聰明」的方法來做到這一點?

正向聲明和使用include directive是正常和正確的方式來打破循環包括。

4

對我來說看起來不錯。在client.h中正向聲明服務器,並在server.h中正向聲明客戶端是正確的。

將兩個頭文件都包含在.c或.cpp文件中是完全正確的 - 您只需避免將頭文件包含在一個圓圈中即可。