关联容器(第3章)(条款21,22)

条款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章

猜你喜欢

转载自blog.csdn.net/u010323563/article/details/112384024
今日推荐