最近很少發文, 因為確實最近有很多事情要做 : 溫書準備考試、準備英語六級測試還有身體也不太好. 於是文章就非常少. 當然也有因為一直都在學習 C++
到目前為止, 博主已經學習完了 《C++ Primer》這本書, 在寫完《C++ 學習筆記》之後, 將會深入 C++. 之後可能會有名字類似於 C++ 進階 的文章, 來更加地深入 C++. 《資料結構》的文章又暫時停止了更新, 這個是因為在造輪子的方面遇到了瓶頸, 即 Allocator 和 Iterator, 雖然這些本身不難, 但是細節方面還有很多地方需要優化. 這個到《資料結構》的文章再詳細說明, 之後也會有關於 Allocator 和 Iterator 的 【C++ 實戰】系列
好了, 正式開始這次的實戰
這次我們將演示複製建構的範例. 這一次的範例有一個背景 : 我們將設計兩個類別, 一個是 Message 類別, 另外一個是 Folder 類別. 我們將所有的訊息都存入 Message 中, 並且將其中一些信息分類存放到 Folder 中. 並且在此之上, 我們需要保證一條 Message 改變的時候, 所有擁有這條 Message 的 Folder 中都會更新顯示
這個模式可以運用在訊息、電子郵件和聊天的 APP 中
我們會為 Message 類別提供兩個成員函式, 用於在 Folder 中移除或者保存一條信息. 也就是說, Message 類別中, 會有一個存儲的空間, 被用於存儲這條 Message 被保存在哪幾個 Folder 中. 當然, Folder 也是如此. 因此, 最好的方法就是將其保存在標準程式庫提供的容器 set 中. 但是如果使用 set<Folder>
或者 set<Message>
進行存儲, 將會產生很多份拷貝. 所以, 我們將使用類別對應的指標型別存入
當一條訊息被保存在一個檔案夾中的時候, 在訊息對應的類別下增加這個檔案夾的名稱, 對應的檔案下增加一條對應的訊息; 當一條訊息從一個檔案中刪除的時候, 這條訊息本身不會刪除, 因為保存的是指標, 只要在訊息對應的物件中刪除對應的檔案, 在對應的檔案中刪除對應的訊息即可
從上面的需求我們可以分析出 : Message 類別中有一個 set<Folder *>
, Folder 類別中有一個 set<Message *>
, 也就是相互包含. 這裡會有一個 C++ 問題 : 相互包含的類別必須以指標表示, 並且實行分離編譯. 也就是說, 加入 A 類別包含 B 類別, B 類別包含 A 類別, 那麼 A 類別中對於 B 類別的宣告必須是 B *, B 類別中對於 A 類別的宣告必須是 A *, 否則他們將相互呼叫, 永無止盡, 產生編譯錯誤. 而且可以分析出, 在一個類別之前, 必須先宣告另外一個類別. 另外, 類別內部的函式實作與類別的定義分離
這條我並沒有放入 《C++ 學習筆記》中. 因為並不屬於 C++ 的一個需要學習的特性, 而是實際編碼過程中的一個坑. 你必須親自過了這個坑, 你才知道如何處理這樣的錯誤
通過上述的需求分析, 我們設計出這樣的類別 :
Mail.hpp
(因為我將其假想成 Mail) :
#ifndef CLIONDEBUG_MAIL_HPP
#define CLIONDEBUG_MAIL_HPP
#include <string>
#include <set>
#include <ctime>
namespace Mail {
using std::string;
using std::set;
string getTime();
constexpr short normal {1};
constexpr short urgent {2};
constexpr short vital {3};
class Folder;
class Message {
friend void swap(Message &, Message &);
private:
void addToFolders(const Message &);
void removeFromFolders();
protected:
set<Folder *> folders;
string context;
string time;
long long int sendingNumber;
long long int receiveNumber;
bool VIP;
short priority;
public:
explicit Message(const string &context = string(), const string &time = Mail::getTime(),
long long int sendingNumber = 0, long long int receiveNumber = 0,
bool VIP = false, short priority = Mail::normal);
Message(const Message &);
Message &operator=(const Message &);
~Message();
void save(Folder *);
void remove(Folder *);
};
class Folder {
friend void swap(Folder &, Folder &);
private:
void addToMessages(const Folder &);
void removeFromMessages();
protected:
set<Message *> messages;
string time;
long long int userNumber;
bool VIP;
short priority;
public:
explicit Folder(const string &time = Mail::getTime(), long long int userNumber = 0,
bool VIP = false, short priority = Mail::normal);
Folder(const Folder &);
Folder &operator=(const Folder &);
~Folder();
void save(Message *);
void remove(Message *);
};
}
#endif //CLIONDEBUG_MAIL_HPP
通過上述類別, 我們可以看到幾個訊息固有的特性, 例如訊息內容、發送方號碼、接收方號碼、時間、優先級等等
我們在實作複製建構的時候, 要特別注意, 將訊息放入對應的 Folder 中; 在實作複製指派的時候, 要特別注意, 此時左側的物件是即將被銷毀的, 所以要從對應的 Folder 中移除它
對於 swap()
成員函式來說, 它們首先從相互的 Folder 中移除, 交換之後重新添加
以下是我實作的程式碼, 沒有經過測試, 僅供參考. 由於這次的程式碼內容對於每一個成員函式或者類別來說並不是特別複雜, 而且文章中也有詳細說明, 所以不再具有註解
Mail.cpp
:
#include "Mail.hpp"
using namespace Mail;
string Mail::getTime() {
auto *t {new time_t {0}};
time(t);
char time[64];
strftime(time, sizeof time, "%Y-%m-%d %H:%M:%S", localtime(t));
delete t;
t = nullptr;
return string(time);
}
void Mail::swap(Message &a, Message &b) {
using std::swap;
for(auto c : a.folders) {
c->remove(&a);
}
for(auto c : b.folders) {
c->remove(&b);
}
swap(a.folders, b.folders);
swap(a.context, b.context);
swap(a.time, b.time);
swap(a.sendingNumber, b.sendingNumber);
swap(a.receiveNumber, b.receiveNumber);
swap(a.VIP, b.VIP);
swap(a.priority, b.priority);
for(auto c : a.folders) {
c->save(&a);
}
for(auto c : b.folders) {
c->save(&b);
}
}
void Mail::swap(Folder &a, Folder &b) {
using std::swap;
for(auto c : a.messages) {
c->remove(&a);
}
for(auto c : b.messages) {
c->remove(&b);
}
swap(a.messages, b.messages);
swap(a.time, b.time);
swap(a.userNumber, b.userNumber);
swap(a.VIP, b.VIP);
swap(a.priority, b.priority);
for(auto c : a.messages) {
c->save(&a);
}
for(auto c : b.messages) {
c->save(&b);
}
}
Message::Message(const string &context, const string &time, long long int sendingNumber, long long int receiveNumber,
bool VIP, short priority) : context(context), time(time), sendingNumber(sendingNumber),
receiveNumber(receiveNumber), VIP(VIP), priority(priority) {
}
Message::Message(const Message &msg) : folders(msg.folders), context(msg.context), time(msg.time),
sendingNumber(msg.sendingNumber), receiveNumber(msg.receiveNumber),
VIP(msg.VIP), priority(msg.priority) {
this->addToFolders(msg);
}
Message &Message::operator=(const Message &msg) {
this->removeFromFolders();
this->folders = msg.folders;
this->context = msg.context;
this->time = msg.time;
this->sendingNumber = msg.sendingNumber;
this->receiveNumber = msg.receiveNumber;
this->VIP = msg.VIP;
this->priority = msg.priority;
this->addToFolders(msg);
return *this;
}
Message::~Message() {
this->removeFromFolders();
}
void Message::addToFolders(const Message &msg) {
for(auto c : msg.folders) {
c->save(this);
}
}
void Message::removeFromFolders() {
for(auto c : this->folders) {
c->remove(this);
}
}
void Message::save(Folder *f) {
this->folders.insert(f);
f->save(this);
}
void Message::remove(Folder *f) {
this->folders.erase(f);
f->remove(this);
}
Folder::Folder(const string &time, long long int userNumber, bool VIP, short priority) : time(time),
userNumber(userNumber),
VIP(VIP), priority(priority) {
}
Folder::Folder(const Folder &f) : messages(f.messages), time(f.time), userNumber(f.userNumber),
VIP(f.VIP), priority(f.priority) {
this->addToMessages(f);
}
Folder &Folder::operator=(const Folder &f) {
this->removeFromMessages();
this->messages = f.messages;
this->time = f.time;
this->userNumber = f.userNumber;
this->VIP = f.VIP;
this->priority = f.priority;
this->addToMessages(f);
return *this;
}
Folder::~Folder() {
this->removeFromMessages();
}
void Folder::addToMessages(const Folder &f) {
for(auto c : f.messages) {
c->save(this);
}
}
void Folder::removeFromMessages() {
for(auto c : this->messages) {
c->remove(this);
}
}
void Folder::save(Message *msg) {
this->messages.insert(msg);
msg->save(this);
}
void Folder::remove(Message *msg) {
this->messages.erase(msg);
msg->remove(this);
}
自創文章, 原著 : Jonny, 如若需要轉發, 在已經授權的情況下請註明出處 :《【C++ 實戰】複製建構範例》https://jonny.vip/2018/05/30/%e3%80%90cplusplus-%e5%af%a6%e6%88%b0%e3%80%91%e8%a4%87%e8%a3%bd%e5%bb%ba%e6%a7%8b%e7%af%84%e4%be%8b/
Leave a Reply