1. 程式人生 > 其它 >關聯容器(第3章)(條款21,22)

關聯容器(第3章)(條款21,22)

技術標籤:《Effective STL》讀書筆記c++

條款21:總是讓比較函式在等值情況下返回false

很顯然,相等的值不存在前後關係,10 < 10肯定是不對的,但是10 <= 10是對的,但是,切記不要講less_equla用來當做關聯容器的比較型別,我想現實中應該也沒有人這麼做。。。如果真有人這麼幹了,會發生什麼呢

根據等價規則: !(10 <= 10) && !(10 <= 10),則是 !(true) && !(true), 那就是false,難道可以往set中插入兩個10??? 畢竟等價規則判斷兩個10不等價。

即使在multiset中也不可以,10 和10不等價,那使用equal_range指定的區間中,10和10就不能出現在一起了??這肯定不是你想要的,所以,別這麼幹,切記不要用less_qual作為關聯容器的比較型別。

必須“嚴格的弱序化”

條款22:切勿直接修改set或者multiset中的鍵

在說 set/multiset之前,我們先看看 map/multimap,const Key,標準規定了,使你無法修改(當然,你一定要改也是有辦法的,但是那不是明智的選擇)

typedef pair<const Key, T> value_type;
template < class Key,                                     // map::key_type
           class T,                                       // map::mapped_type
           class Compare = less<Key>,                     // map::key_compare
           class Alloc = allocator<pair<const Key,T> >    // map::allocator_type
           > class map;

而 set/multiset 標準中沒有這樣的限制

template < class T,                        // set::key_type/value_type
           class Compare = less<T>,        // set::key_compare/value_compare
           class Alloc = allocator<T>      // set::allocator_type
           > class set;

那我們嘗試修改看看,定義一個員工類,以員工唯一的員工編號作為排序規則,目的是員工的其他內容可以修改,很顯然,一個員工的職級可以調整,很遺憾,我所使用的G++是無法通過編譯的,也許其他編譯器可以,那也不具有移植性。

不過我們可以通過強制型別轉換來修改,這樣可以保證在不同的編譯器之間都可以使用(然後這樣真的好麼,肯定不好)。對於員工型別來說,沒有修改排序用的鍵值部分,看起來沒什麼影響,然而,對於int,容器中的元素已經沒有順序性了,這顯然不是我們所希望看到的。

    // 員工類
    class Employee {
    public:
        Employee(int i) : mId(i), mTitle("") {
            std::cout << "Employee construct..." << std::endl;
        }

        Employee(const Employee &other) : mId(other.id()), mTitle(other.title()) {
            std::cout << "Employee copy construct..." << std::endl;
        }

        ~Employee() {
            std::cout << "Employee destruct..." << std::endl;
        }

        int id() const {
            return mId;
        }

        const string &title() const {
            return mTitle;
        }

        void setTitle(const string &title) {
            mTitle = title;
        }

    private:
        int mId;
        string mTitle;
    };

    // 員工類的比較型別
    struct IDLess {
        bool operator()(const Employee &lhs, const Employee &rhs) const {
            return lhs.id() < rhs.id();
        }
    };

    void test_22() {
        // int型別set嘗試修改
        set<int> iset{1, 2, 3, 4, 5};
        auto iter = iset.find(2);
        if (iter != iset.end()) {
//            *iter = 20; // 無法通過編譯,返回的迭代器是const型別
            const_cast<int &>(*iter) = 10; // 強制型別轉換,對返回的const引用去掉const屬性後修改
        }

        // 員工型別set嘗試修改非鍵值部分
        set<Employee, IDLess> eset;
        Employee e1(1); // 構造,稍後析構
        eset.insert(e1); // 拷貝構造1
        eset.insert(Employee(2));  // 臨時物件構造2,拷貝構造到容器2,析構臨時物件2
        eset.emplace(3); // 直接在容器中構造3
        eset.emplace_hint(eset.find(3), 4);  // 臨時物件構造用於查詢3,臨時物件析構3,直接在容器中構造4
        eset.insert(std::move(Employee(5))); // 本來是想構造的物件直接移動到容器中,奈何容器沒有提供move版本的insert,並沒用

        auto iter1 = eset.find(4);
        if (iter1 != eset.end()) {
//            iter1->setTitle(); // 錯誤,不能修改,返回的迭代器是const型別
            const_cast<Employee &>(*iter1).setTitle("Corporate Deity"); // 強制型別轉換,對返回的const引用去掉const屬性後修改
        }

        return;
    }

看看這個修改後奇怪的int型別set

即使如 Employee 型別的修改沒有觸及鍵值部分,這也是不提倡的,引出本條款的最佳實踐。

stet1:找到需要修改的元素;

step2:做一個備份;

step3:修改備份的副本;

step4:將元素從容器中刪除;

step5:把修改後的備份元素從新插入到容器中,根據剛才找到的位置(常數時間),如果不提示位置,就是對數時間;

最佳實踐例子

   void test_22() {
        // int型別set嘗試修改
        set<int> iset{1, 2, 3, 4, 5};
        auto iter = iset.find(2);
        if (iter != iset.end()) {
//            *iter = 20; // 無法通過編譯,返回的迭代器是const型別
//            const_cast<int &>(*iter) = 10; // 強制型別轉換,對返回的const引用去掉const屬性後修改
            // 最佳實踐
            int cp = *iter;
            cp = 20;
            iset.erase(iter++);
            iset.insert(iter, cp); // 當然,這個提示其實沒有什麼用,僅是展示
        }

        // 員工型別set嘗試修改非鍵值部分
        set<Employee, IDLess> eset;
        Employee e1(1); // 構造,稍後析構
        eset.insert(e1); // 拷貝構造1
        eset.insert(Employee(2));  // 臨時物件構造2,拷貝構造到容器2,析構臨時物件2
        eset.emplace(3); // 直接在容器中構造3
        eset.emplace_hint(eset.find(3), 4);  // 臨時物件構造用於查詢3,臨時物件析構3,直接在容器中構造4
        eset.insert(std::move(Employee(5))); // 本來是想構造的物件直接移動到容器中,奈何容器沒有提供move版本的insert,並沒用

        auto iter1 = eset.find(4);
        if (iter1 != eset.end()) {
//            iter1->setTitle(); // 錯誤,不能修改,返回的迭代器是const型別
//            const_cast<Employee &>(*iter1).setTitle("Corporate Deity"); // 強制型別轉換,對返回的const引用去掉const屬性後修改
            // 最佳實踐
            Employee cp(*iter1);
            cp.setTitle("Manager");
            eset.erase(iter1++);
            eset.insert(iter1, cp);
        }

        return;
    }

參考:《Effective STL中文版》第3章