effective stl 阅读笔记

    继effective c++和more effective c++之后,meyer又推出effective stl。由于之前我一直工作在symbian平台(不支持stl)和android平台(即使在c层也不支持完善的stl)一直没有动力去学习 stl。目前我现在从事后台开发工作,要接触linux下的标准c++开发,stl是有利的武器,所以,学习meyer的这本书是必须的了。断断续续的看了一段时间,记下了一些自认为的重点,希望自己能持续学习下去。

一。尽量用

      typedef vector<string> StrContainer;

     typedef StrContainer::iterator StrContainerIter;

 这样看着美观。

二。尽量使用for_each来代替自己遍历

三。使用reserve来提前分配内存:如果事先知道容器将要盛放的最大数量,可以
   vector<int> v;
   v.reserve(100);
   for(int i = 0; i<100;++i)
  {
     v.push_back(i);
  }
  vector在重新分配发生时一般把容量翻倍,在上面代码中,就不会发生重分配。但如果没有调用reserve,可能出现10次重分配。

四。string,就像所有的标准stl容器,是没有虚析构函数的,所以不要继承它。

五。要注意线程安全的问题。

六。 异常安全的代码。《当使用new得指针的容器时,记得在销毁容器前delete那些指针》
 void dosSomething()
 {
    vector<Widget*> v;
    for(int i = 0; i < N; ++i)
    {
       v.push_back(new Widget);
    }
    // do something ...
    typedef vector<Widget*>::itertor WidgetIter;
    for(WidgetIter it = v.begin();it != v.end();++it)
    {
        delete *it;
    }
 }
 以上代码有问题吗?
 1.用for_each 试试?
  template<typname T>
  struct DeleteObject:public unary_function<const T*,void>
  {
     void operator() (const T* ptr) const
     {
         delete ptr;
     }
   };
  然后写成这样:
  void doSomething()
  {
    ..//同上
    for_each(v.begin();v.end();DeleteObject<Widget>);
  }
  有问题吗?? 当然,如果Widget继承了一个类,而该类又没有虚析构函数,那自然又会内存泄漏了。
 2.这段代码是非异常安全的,在v被填充到删除前如果发生异常,则会资源泄漏。


  最终要如此写
  void doSomething()
 {
   typedef boost::shared_ ptr<Widget> SPW; //SPW = "shared_ptr to Widget"
   vector<SPW> vwp;
   for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
      vwp.push_back(SPW(new Widget)); // 从一个Widget建立SPW,然后进行一次push_back
   
   ... // 使用vwp
  } // 这里没有Widget泄漏,甚至在上面代码中抛出异常


七。在map::operator[] and map::insert之间仔细选择。


class Widget {
public:
Widget();
Widget(double weight);
Widget& operator=(double weight);
...
};
map<int, Widget> m;
m[1] = 1.50;
m[2] = 3.67;
m[3] = 10.5;
m[4] = 45.8;
m[5] = 0.0003;


  map的operator[]函数是个奇怪的东西。它与vector、deque和string的operator[]函数无关,也和内建的数组operator[]无关。相反,map::operator[]被设计为简化“添加或更新”功能。即给定:std::map<K,V> m;
 m[k] = v; 表示如果map中不存在k,则添加。如果存在,则更新k对应的值为v。这项工作的原理是operator[]返回一个与k关联的值对象的引用。然后v赋值给所引用(从operator[]返回的)的对象。当要更新一个已存在的键的关联值时很直接,因为已经有operator[]可以用来返回引用的值对象。但是
如果k还不在map里,operator[]就没有可以引用的值对象。那样的话,它使用值类型的默认构造函数从头开始建立一个,然后operator[]返回这个新建立对象的引用。
  代码m[1] = 1.5;
  等价于
  typedef map<int, Widget> IntWidgetMap;
  pair<IntWidgetMap::iterator,bool> result = m.insert(IntWidgetMap::value_type(1,Widget()));
  result.first->second = 1.5;
先默认构造了一个Widget,然后给它赋新值。如果用想要的值直接构造Widget,比先默认构造再赋值要高效。所以我们应该:
  m.insert(IntWidgetMap::value_type(1,1.5));
这与上面的那些代码有相同的最终效果,除了它通常节省了三次函数调用:一个建立临时的默认构造Widget
对象,一个销毁那个临时的对象和一个对Widget的赋值操作。那些函数调用越昂贵,你通过使用map-insert代
替map::operator[]就能节省越多。


综上:我早先谈及的operator[]被设计为简化“添加或更新”功能,而且现在我们理解了当“增加”被执行时,insert比operator[]更高效。当我们做更新时,情形正好相反。如果你要更新已存在的map元素,operator[]更好,但如果你要增加一个新元素,insert则有优势。


八。考虑用有序vector代替关联容器。
   关联容器(map,set,multiset,multimap)比较消耗内存。因为它们内部都是平衡二叉树,每个树节点会保存左右孩子节点以及父亲节点,这样会多出3个指针来。而vector则是物理连续的。对于查找多于删除,添加混合操作的情况,用vector(有序的)甚至会比map快。


九。避免原地修改set和multiset的键
  这对于map和multimap特别简单,因为试图改变这些容器里的一个键值的程序将不能编译:
  map<int,string> m;
  m.begin()->first = 10 ;//编译错误


  multimap<int,string> mm;
  mm.begin()->fisrt = 20; //编译错误


那是因为map<K, V>或multimap<K, V>类型的对象中元素的类型是pair<const K, V>
如果你使用一个const_cast,正如我们将在下面看到的,你或许能改变它。不管相信与否,有时那是你想要做的。


但是它对set和multiset却是可能的。对于set<T>或multiset<T>类型的对象来说,储存在容器里的元素类型只不过是T,并非const T。实际上,事情不完全那么简单,但我们不久将看到。没理由超过自己。我们先得会爬,然后才能在碎玻璃上爬。


EmpIDSet::iterator i = se.find(selectedID);
if (i != se.end()) 
{
   i->setTitle("Corporate Deity"); // 有些STL实现会拒绝这样,因为*i是const
}
为了让它可以编译并且行为正确,我们必须映射掉*i的常量性。这是那么做的正确方法:
EmpIDSet::iterator i = se.find(selectedID);
if(i != se.end())
{
   const_cast<Employee&>(*i).setTitle("Corporate Deity");//映射掉*i的常量性
}
很多人想到的是这段代码:
if(i != se.end())
{
   static_cast<Employee>(*i).setTitle("Corporate Deity");//*i映射到一个Employee
}
或者
if(i != se.end())
{
    (Employee)(*i).setTitle("Corporate Deity");//同上,但使用C的映射语法
}
上面等同于
if (i != se.end()){
Employee tempCopy(*i); // 把*i拷贝到tempCopy
tempCopy.setTitle("Corporate Deity"); // 修改tempCopy
}
现在应该清楚映射到引用的重要性了吧。通过映射到引用,我们避免了建立一个新对象。取而代之的是,映
射的结果是一个现有对象的引用,i指向的对象。


刚才写的适用于set和multiset,但当我们转向map和multimap时,细节变粗了。注意map<K, V>或
multimap<K, V>包含pair<const K, V>类型的元素。那个const表明pair的第一个组件被定义为常量,而那意味着
试图修改它是未定义的行为(即使映射掉它的常量性)。理论上,一个STL实现可能把这样的值写到一个只
读的内存位置(比如,一旦写了就通过系统调用进行写保护的虚拟内存页),而且试图映射掉它的常量性,
最多,没有效果。我从未听说一个实现那么做,但如果你是一个坚持遵循标准拟定的规则的人,你绝不会试
图映射掉map或multimap键的常量性。


十。不能用vector<bool> ,用不了。
vector<bool>不满足STL容器的必要条件,你最好不要使用它;而deque<bool>和bitset是基本能满足你对
vector<bool>提供的性能的需要的替代数据结构。


十一。如何将vector和string的数据传给遗留的API
最常见的一个就是已经存在的遗留的C风格API接受的是数组和char*指针,而不是
vector和string对象。这样的API函数还将会存在很长时间,如果我们要有效使用STL的话,就必须和它们和平
共处。
vector中的元素被C++标准限定为存储在连续内存中,就像是一个数组,所以,如果我们想要传递v给这样的C风格的API:
void doSomething(const int* pInts, size_t numInts);
我们可以这么做:
doSomething(&v[0], v.size());


唯一的问题就是,如果v是空的。如果这样的话,v.size()是0,而&v[0]试图产生一个指向根
本就不存在的东西的指针。这不是件好事。其结果未定义。一个较安全的方法是这样:
if (!v.empty()) 
{
    doSomething(&v[0].v.size());
}
如果你正在和告诉你使用v.begin()代替&v[0]的人打交道的话,你该重新考虑一下你的社交圈了。


类似从vector上获取指向内部数据的指针的方法,对string不是可靠的,因为(1)string中的数据并没有保证被
存储在独立的一块连续内存中,(2)string的内部表示形式并没承诺以一个null字符结束。这解释了string的成
员函数c_str存在的原因,它返回一个按C风格设计的指针,指向string的值。因此我们可以这样传递一个string
对象s给这个函数,
void doSomething(const char *pString);
像这样:
doSomething(s.c_str());
即使是字符串的长度为0,它都能工作。在那种情况下,c_str将返回一个指向null字符的指针。


**即使字符串内部自己内含null时,它同样能工作。但是,如果真的这样,doSomething很可能将第一个内含的null解释为字符
串结束。string对象不在意是否容纳了结束符,但基于char*的C风格API在意。


只有vector承诺了与数组具有相同的潜在内存分布.


vector和string的数据只能传给只读取而不修改它的API。这到目前为止都是最安全的事情。对于string,这也是唯一可做的,因为没有承诺说c_str产生的指针指在string数据的内部表示形式上;它可以返回一个指针指向数据的一个不可修改的拷贝,这个拷贝满足C风格API对格式的要求。


十二。使用“交换技巧”来修整过剩容量
class Contestant {...};
vector<Contestant> contestants;


如果你的vector有时候容纳了10万个的可能的候选人,它的容量会继续保持在至少100,000,即使后来它只容纳10个。


这是你怎么修整你的竞争者vector过剩容量的方法:
  vector<Contestant>(contestants).swap(contestants);
表达式vector<Contestant>(contestants)建立一个临时vector,它是contestants的一份拷贝:vector的拷贝构造函数做了这个工作。
但是,vector的拷贝构造函数只分配拷贝的元素需要的内存,所以这个临时vector没有多余的
容量。然后我们让临时vector和contestants交换数据,这时我们完成了,contestants只有临时变量的修整过的容
量,而这个临时变量则持有了曾经在contestants中的发胀的容量。在这里(这个语句结尾),临时vector被销
毁,因此释放了以前contestants使用的内存。瞧!收缩到合适。


同样可以用于string
string s; //s多余的空间很多


string(s).swap(s); //s收缩到“正好”。


十三。分配器
分配器最初被设想为抽象内存模型,在那种情况下,分配器在它们定义的内存模型中提供指针和引用的typedef才有意义。
在C++标准里,类型T的对象的默认分配器(巧妙地称为allocator<T>)提供
typedef allocator<T>::pointer和allocator<T>::reference,
而且也希望用户定义的分配器也提供这些typedef。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值