Item 2:抛弃写“容器无关”的代码的幻想

2024-01-07 11:58

本文主要是介绍Item 2:抛弃写“容器无关”的代码的幻想,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

标题  Item 2:抛弃写“容器无关”的代码的幻想     选择自 pongba 的 Blog
关键字  Item 2:抛弃写“容器无关”的代码的幻想
出处 

                            Item 2:抛弃写“容器无关”的代码的幻想

scott meyers   著
                                          刘未鹏          译
                                    
 

    STL建立在泛型的基础之上。数组(arrays)被泛化成为容器,并以它们所包含的对象的类型为模板参数。函数(functions)被泛化成算法,并以它们所接纳的迭代器的类型为模板参数。指针被泛化成迭代器,并以它们所指向的对象的类型为模板参数。

   

    然 而这仅仅是个开始。个别的容器类型被泛化成为序列式容器和关联式容器。相似的容器被赋予相似的泛函性(functionality,或“功能性”)。标准 的“内存连续式”容器提供随机存取迭代器,标准的node-based容器提供双向迭代器。序列式容器支持push_front或push_back,关 联式容器却不支持。关联式容器提供算法复杂度为对数时间的 lower_bound,upper_bound和equal_range成员函数,然而序列式容器却不提供。

   很 自然的,你也想加入这个泛化的行动中来,这值得赞扬,并且当你写你自己的容器、迭代器和算法时,你当然想尝试着这样做。然而有许多程序员却用错了方式—— 他们并不是让代码依赖于某种特定的容器,而是试图将容器的概念泛化,从而试图达到这样一种目的——当前使用某一种容器,如vector,以后又能够在不改变其它代码的前提下透明地将容器替换成其它类型(如换成deque、list等)。这也就是说,他们努力去写“ 容器无关”的代码。这种泛化虽然意图是好的,然而——几乎可以肯定的说——这是个误导

    即 使是“容器无关”代码的最热心的鼓吹者不久也会意识到:试图去写能同时和序列式容器及关联式容器工作的代码几乎是没有意义的。许多成员函数只在某一种特定 类型的容器中存在。例如:只有序列式容器支持push_front和push_back,只有关联式容器支持count和lower_bound,等等。 即使是erase和insert这样基本的操作在不同容器类别之间都有着语义上的差别。比如,当你插入一个元素至序列式容器中时,它将会呆在你将它放置的地方。然而在关联式容器中,它会按照容器的排序方式被移至某个恰当的地点。再举个例子,在序列式容器上调用erase会返回一个新的迭代器,而在关联式容器上则什么也不返回(条款9说明了这将如何影响你的代码)。

   

    好吧,假设你热切盼望能够写出能与大多数通常的容器如vector、list、deque一起工作的代码。显然,你的代码必须只用到它们的能力的交集(译 注:假设你使用了容器A有而容器B没有的功能,则当容器A换成B时你的代码将不能工作)——这意味着你得放弃使用reserve和capacity(条款 14),因为deque和list并不提供它们。有list的到场意味着你又不能使用operator[],你还把自己限制在双向迭代器的能力范围内—— 那又意味着你将不能使用需要随机存取式迭代器的算法(例如:sort,stable_sort,partial_sort,nth_element(条款 31))。

    另 一方面,你支持vector的愿望将push_front和pop_front踢出场外(译注:因为vector根本没有这两个操作),如果你希望支持 deque或vector,则你将不能使用splice和成员形式的sort。后者(译注:即不能使用splice和成员形式的sort)意味着在你的 “泛化的序列式容器”上你不能调用任何形式的sort。

    以上只是一些较为明显的事实。如果你违反其中任何一条限制,在你所要使用的所有容器中至少有一种会使你的代码通不过编译。而那些能通过编译的代码则会隐藏着更大的危险。

    导 致这些问题的罪魁祸首就是各种序列式容器间有关迭代器、指针、引用失效的不同协定。要写出能正确地和vector,deque,list同时工作的代码, 你必须假设任何操作都会使所有的迭代器、指针、引用失效。因此,你必须假设在你所使用的任何容器上调用insert都会使它们的任何迭代器、指针、引用失 效——而这不过仅仅是由于deque::insert()会使所有迭代器失效。并且由于缺少调用capacity的能力,你就必须假设vector:: insert()会使指向它内部的所有指针和引用失效(条款1解释了对于deque——某些时候——在它的迭代器失效的同时指针和引用却不失效)。出于类 似的原因,每次调用erase你得做同样的打算。

   

    想 要知道更多吗?你不能将容器中的数据传给C风格的接口(C-interface)。因为仅有vector支持这种行为(条款16),你不能存储bool型 别的变量,因为正如条款18所解释的,vector<bool>并不总是表现得像个vector,它也从不真的存储bool值。你不能假定 list的常数时间的插入和擦除动作,因为vector和deque在这些操作上需要耗线性时间。

   

    当 所有该说的都说完了,该做的都做完了,你就只剩下了如下的这个“泛化的序列式容器”:在这个容器上,你不能调用reserve,capacity, operator[],push_front,pop_front,splice,或是任何需要接受随机存取迭代器的算法;你得假设任何insert和 erase会消耗线性时间,并会使所有迭代器、指针、引用失效;你还得假设这个容器与C风格不兼容,并且不能保存bool值。这真的是你期望在程序中使用 的容器吗?我怀疑不是!

   

    如 果你收敛一下野心,决定愿意放弃对list的支持,则你仍然要放弃reserve,capacity,push_front,pop_front;你仍然 必须假定所有对insert和erase的调用消耗线性时间,并使所有迭代器、指针、引用失效。你仍然失掉与C风格的兼容;你仍然不能存储bool值。

    如 果你放弃序列式容器,然后争取让你的代码能和不同的关联式容器一同工作,情况并不会变得更好一些——写同时支持set和map的代码近乎不可能。因为 sets存储单独的对象,而maps存储一对对象。甚至写同时支持set和multiset(或是map和multimap)的代码都是艰难的。因为只接 受一个值的insert成员函数在set/map和它们的兄弟multiset/multimap身上具有不同的返回值型别。你还必须避免作出对“到底有 多少个值的拷贝存在于容器中”的假定。你不能调用operator[]——因为它只在map中存在。

    面对事实吧——不值得这样做!不同的容器是处于两个不同世界的东西,有各自的规则,有不同的强大之处和弱点。它们并非被设计为可互换的。你对此无能为力。如果你去尝试,你只不过是在冒险,而且注定经不起考验。

    当 你意识到你所选择的容器是不理想的,你得去使用别的类型的容器时,黎明就到来了。你现在知道了,当你改变所使用的容器时,你不只是要改正编译器给你诊断出 的错误,你还需要检查使用新容器的所有代码,看看在新容器的性能特征和iterator,pointer,reference失效的规则之下还有什么需要 改变的。如果你从vector转而使用别的容器,你得确认你已经不再依赖于vector的C风格兼容的内存分配形式。如果你从别的容器转向vector, 你得确认你没有使用它来存储bool值。

    鉴于有时你必须得改变容器,你可以使这种改变更容易一些,通常,经过包装,包装,再包装。最简单的方法之一,是对容器和迭代器使用typedefs的自由形式,因此将下面的代码:

         class Widget{...};

         vector<Widget> vw;

         Widget bestWidget;

         ...                       //give bestWidget a value

         vector<Widget>::iterator i=    //find a Widge with the

           find(vw.begin(),vw.end(),bestWidget);//same value as bestWidget

取代为

         class Widget{...};

         typedef vector<Widget> WidgetContainer;

         typedef WidgetContainer::iterator WCIterator;

         WidgetContainer cw;

         Widget bestWidget;

         ...

         WCIterator i=find(cw.begin(),cw.end(),bestWidget);

    这使改变容器的类型变得相当容易。当只是给容器加一个自定义配置器时(这样的改变不会改变容器的iterator/pointer/reference失效规则),这会显得特别方便。

             class Widget{...};

             template<typename T>                        //see Item 10 for why this

             SpecialAllocator{...};                      //needs to be a template

             typedef vector<Widget,SpecialAllocator<Widget> > WidgetContainer;

             typedef WidgetContainer::iterator WCIterator;

             WidgetContainer cw;                          //still works

             Widget bestWidget;

             ...

             WCIterator i=find(cw.begin(),cw.end(),bestWidget); //still works

    如果这些对你并不代表什么,你也得感激它们能为你省下的工作,举个例子,如果你有个型别为:

             map<string,vector<Widget>::iterator,CIStringCompare> 

               //CIStringCompare is "case-insensitive string compare;"

               //Item 19 describes it

的对象你想用const_iterator遍历map你真的想拼写下面的代码

                       map<string,vector<Widget>::iterator,CIStringCompare>::const_iterator

    如果你要用到不止一次呢?一旦你用STL一段时间后,你会意识到typedef是你的朋友。

    一个typedef只是为其他的型别起一个别名。所以这只是提供纯粹字面上的包装。一个typedef并不阻止客户端做(或依赖)他们已经能做(或依赖)的。如果想要避免将你对容器的选择暴露给客户端,你需要更强力的措施——class

    

    当你用一个容器替换现有容器时为了限制所要改动的代码的量你可以将容器隐藏在class内部,并限制从class的接口显露出的容器相关container-specific的信息的量。例如,如果你需要创建一个顾客列表(customer list),不要直接用list(指STL容器的list)。你可以创建一个新的CustomerList类,将list作为它的私有成员隐藏起来。

            class CustomerList{                        

            private:                                   

            typedef list<Customer> CustomerContainer;

            typedef CustomerContainer::iterator CCIterator;

            CustomerContainer customers;

            public:           //limit the amount of list-specific

            ...               //infomation visible through this interface

};

    起初这看起来可能有些莫名其妙——毕竟一个customer list也是一个list,不是么好吧或许再后来你会发现你并不需要像你预期的那样频繁地从顾客列表中部增加或删减消费者但是你开始需要快速标识出你的顾客中的前20%(可能是消费量最大的)--这正是nth_lement算法特定的任务(条款31)然而nth_element需要随机存取迭代器所以它不能和list合作(译注list的迭代器是双向的却不是随机的)在这种情况下你的customer "list"最好用一个vectordeque来实现。

当你考虑了这种变化后你还得检查CustomerList的每个成员函数和友员函数以考察它们所受到的影响(因底部容器的性能因素和iterator/pointer/reference失效性)作出可能需要的相应的改变。但是如果你将CustomerList的实现细节封装得很好,则CustomerList内部容器的变化对客户端的影响就会很小。你不能写出容器无关(container-independent)的代码,但你的客户或许可以(译注:在上例中,如果CustomerList封装得够好的话)。


作者Blog: http://blog.csdn.net/pongba/
相关文章

这篇关于Item 2:抛弃写“容器无关”的代码的幻想的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/579834

相关文章

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Java中Map.Entry()含义及方法使用代码

《Java中Map.Entry()含义及方法使用代码》:本文主要介绍Java中Map.Entry()含义及方法使用的相关资料,Map.Entry是Java中Map的静态内部接口,用于表示键值对,其... 目录前言 Map.Entry作用核心方法常见使用场景1. 遍历 Map 的所有键值对2. 直接修改 Ma

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

IIS 7.0 及更高版本中的 FTP 状态代码

《IIS7.0及更高版本中的FTP状态代码》本文介绍IIS7.0中的FTP状态代码,方便大家在使用iis中发现ftp的问题... 简介尝试使用 FTP 访问运行 Internet Information Services (IIS) 7.0 或更高版本的服务器上的内容时,IIS 将返回指示响应状态的数字代

MySQL 添加索引5种方式示例详解(实用sql代码)

《MySQL添加索引5种方式示例详解(实用sql代码)》在MySQL数据库中添加索引可以帮助提高查询性能,尤其是在数据量大的表中,下面给大家分享MySQL添加索引5种方式示例详解(实用sql代码),... 在mysql数据库中添加索引可以帮助提高查询性能,尤其是在数据量大的表中。索引可以在创建表时定义,也可

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元