C++ 介面應在型別與簽章中編碼擁有權、生命週期與可替代性。C++ Core Guidelines 的零法則正是源自這項設計:由 RAII 成員組成的領域值,不應手動重現它們的複製、移動與解構行為。
資源擁有者與領域值 #
資源擁有型別會取得一項資源,在解構函式中釋放它,並宣告是否允許複製或轉移。控制代碼表示對資源的存取;擁有型控制代碼也負有釋放責任。當標準擁有者無法表示資源的取得、釋放或錯誤政策時,適合使用自訂擁有型別:
#include <cstdio>
#include <utility>
class File {
public:
explicit File(std::FILE* owned_handle) noexcept : handle_(owned_handle) {}
~File() { if (handle_) std::fclose(handle_); }
File(const File&) = delete;
File& operator=(const File&) = delete;
File(File&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
File& operator=(File&& other) noexcept {
if (this != &other) {
if (handle_) std::fclose(handle_);
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
private:
std::FILE* handle_;
};堆疊展開期間解構函式不得拋出例外。當呼叫端必須觀察釋放失敗時,提供明確的 close() 類操作,然後讓解構函式執行不拋出例外的清理。確保自訂移動操作會轉移資源,並讓來源物件處於可安全解構的狀態;預設的指標移動會複製原始指標,因此對擁有型原始控制代碼而言是不正確的。若 std::unique_ptr 搭配自訂刪除器可以表達該擁有權,請優先使用它。
領域值應由正確的擁有者組成:
#include <string>
#include <utility>
#include <vector>
class Entry {
public:
explicit Entry(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
std::vector<std::string> labels_;
};因為 std::string 與 std::vector 已提供值語意,Entry 不需要自訂解構、複製或移動操作。
以繼承表達介面 #
公開繼承表示介面層級的「是一種」關係。只有當基底的每個有效操作對衍生型別仍然有效時才使用它;若只是重複使用實作而不需要可替代性,請使用組合。
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};當用戶端可以透過基底指標銷毀衍生物件時,多型基底需要虛擬解構函式。對預期的覆寫標記 override;只有在刻意封閉擴充邊界時才套用 final。保持表示法私有,因為可變表示法的存取會像公開資料一樣直接削弱不變量。
將參數視為擁有權契約 #
在考慮複製成本之前,先依擁有權與生命週期需求選擇參數形式:
| 參數 | 契約 |
|---|---|
T | 被呼叫端取得自己的值;適合廉價值或被呼叫端會保留複本的情況。 |
const T& | 不擁有的唯讀借用;呼叫端在呼叫期間負責維持生命週期。 |
T& | 非空的可變借用;被呼叫端可以修改參照物件,但不擁有它。 |
T* | 可以為 null 的借用;null 具有已文件化的意義。 |
std::unique_ptr<T> 以值傳遞 | 擁有權轉移給被呼叫端。 |
std::shared_ptr<T> 以值傳遞 | 建立或延長共用擁有權;只有在需要共用生命週期時使用。 |
以值接收 std::string 並將它移動到儲存空間中,能為左值與右值呼叫端有效地表達獨立保留的值:
#include <string>
#include <utility>
class User {
public:
explicit User(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};不要使用原始指標掩蓋擁有權轉移。當呼叫端接手責任時,回傳 std::unique_ptr<T> 等擁有型值;只有在 API 能說明並維持被借用物件的生命週期時,才回傳參照或不擁有的指標。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)