實作慣用法應明確表達擁有權、生命週期或相依性邊界。只有在相關的失效與複雜度規則仍對讀者可見時,簡潔語法才有用。
序列容器的 Erase-Remove #
std::remove 與 std::remove_if 是演算法;它們會重新排列範圍,使保留的元素佔據前綴,並回傳新的邏輯結尾。兩者都不會刪除元素,也不會變更容器大小。容器提供範圍 erase 多載時,請立即刪除尾端。
std::vector<int> values{1, 2, 2, 3, 4};
values.erase(std::remove(values.begin(), values.end(), 2), values.end());
values.erase(std::remove_if(values.begin(), values.end(), is_invalid), values.end());自 C++20 起,std::erase(values, value) 與 std::erase_if(values, predicate) 可在支援它們的標準容器中表達完整作業。專案語言與函式庫基線提供這些多載時,請優先使用。
移除演算法可能移動或指定保留的元素,因此指向位置變更之值的參照與迭代器不再識別相同的值。後續的 erase 遵循容器的失效規則。對 std::vector 而言,刪除位置及其後的迭代器、指標與參照都會失效;程式碼必須重新取得它們。節點容器具有不同規則,因此不要將 vector 的假設套用到 std::list、std::map 或 std::unordered_map。
Pimpl 定義表示邊界 #
實作指標慣用法會將私有表示從公開標頭檔中移除。用戶端會針對穩定介面編譯,而不是每個私有成員型別,因此表示變更可以減少重建;當其他公開 ABI 也受到控制時,還能維持 ABI 邊界。
// widget.hpp
class Widget {
public:
Widget();
~Widget();
Widget(Widget&&) noexcept;
Widget& operator=(Widget&&) noexcept;
Widget(const Widget&);
Widget& operator=(const Widget&);
private:
class Implementation;
std::unique_ptr<Implementation> implementation_;
};
// widget.cpp
class Widget::Implementation {
// Private state and dependencies.
};
Widget::~Widget() = default;std::unique_ptr<Implementation> 允許標頭檔使用前置宣告,但解構需要在刪除器實例化的位置讓 Implementation 完整。在 Implementation 定義之後於類別外定義解構函式,即可提供完整性。複製作業需要明確的深層複製原則,因為 unique_ptr 不可複製;若 Widget 有意設計為只能移動,請省略複製作業。
Pimpl 有成本:一般形式需要一次配置、指標間接、較差的區域性,以及可能更複雜的除錯。它也不會讓每一項實作變更都具備 ABI 安全性:內嵌函式、例外規格、虛擬函式、基底類別與暴露的參數型別仍是契約的一部分。
不使用 Pimpl 的相依性降低 #
使用能保護預期契約的最輕量邊界:
- 標頭檔只儲存類別指標或參照時,對類別使用前置宣告。
- 呼叫端需要行為而不是表示時,依賴狹窄介面。
- 只有在可接受編譯期耦合時,才將樣板定義與值成員放入標頭檔。
- 具體私有配置、昂貴的相依圖或 ABI 承諾合理化執行期間接時,使用 Pimpl。
型別別名可能像資料成員一樣洩漏表示。回傳 std::vector<T>::iterator 會讓用戶端承諾使用 vector 的迭代器型別;之後改變容器就成為來源變更,也可能是 ABI 變更。
間接與值語意 #
直接值成員具有單一配置域、清楚的擁有權,且通常具備較佳的快取區域性。std::optional<T> 表示可能不存在但不需分離配置的就地值。std::unique_ptr<T> 表示對分離配置物件的獨佔擁有權,適合多型、不完整型別、大型選擇性狀態或不同的識別。
因為物件需要這些語意屬性才選擇間接,不要只因成員很大就選擇。間接會使可為空性、配置失敗、指標生命週期與額外的別名層成為實作的一部分。值語意則使複製與移動成為類別契約的一部分;呼叫端需要獨立值時,請選擇值語意。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)