Effective STL 中文版记录(二)

Effective STL 章节(二)

——50条有效使用STL的经验(12/50)



第六条、当心C++编译器最烦人的分析机制

C++的编译过程包括四个步骤:预处理、编译、汇编、链接

这其中编译过程是最核心的工作,然而这里可能存在一种情况:编译器误解了代码

class Test
{...};

int main()
{
	Test t();
}

这里Test t();本意是希望调用默认构造函数来创建对象,但是可能会被编译器误解为声明了一个以Test为返回值的函数

这就会导致对象无法创建成功,这是不可接受的

所以建议改为:

Test t{}; // C++ 11 and higher only
Test t;
Test t = Test();

这三种方法都是可行的

第七条、如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete

有点长,分析一下:
“容器中包含了通过new操作创建的指针”指的是:

vector<Test *> v;
while(1)
{
	v.push_back(new Test);
	...
	break;
}

这里的vector就包含了new创建的指针,当它进行析构时会怎样呢?

vector的所有元素会被释放,但是在push时创建的对象并不会被删除,这是事实,那么也就导致了内存溢出问题

所以在vector析构之前依次进行delete就行,真正执行可能会出现其他的问题,不过一般不会遇到,有这个思想即可

第八条、切勿创建包含auto_ptr的容器对象

不用太担心,如果定义vector<auto_ptr<Test>> test;这种变量,大概率编译器是不会通过的,知道这点即可

原因是auto_ptr的所有权转移语义,当它被拷贝时,原本的智能指针会失去对原始指针的所有权

std::auto_ptr<int> p1(new int(10));
std::auto_ptr<int> p2 = p1; // p1 的所有权转移到 p2,p1 变成了空指针

C++ 11已经抛弃了这个指针,而是用std::unique_ptr代替,所以没什么关系

第九条、慎重选择删除元素的方法

这一点是从效率出发考虑的,根据容器的不同来选择最高效的删除方法

1) 删除容器特定值的所有对象:
若为vector、string等:erase-remove方法
若为list:使用list::remove
若为标准关联容器:使用erase成员函数

2) 删除容器满足特别判定式的所有对象:
若为vector、string等:erase-remove_if方法
若为list:使用list::remove_if
若为标准关联容器:使用remove_copy_if和swap

3) 在循环内部进行操作:
若是标准序列容器:写循环遍历,并且调用erase时,要用它的返回值更新迭代器
若是标准关联容器:写循环遍历,并且调用erase时,要对迭代器做后缀递增

第十条、了解分配子(allocator)的约定和限制

什么是分配子?

std::allocator 是 C++ 标准库中的一种内存分配器(allocator)
用于为容器(如 std::vector, std::list, std::deque 等)管理内存的分配和释放
C++ 标准容器默认使用 std::allocator 来分配和释放内存

类似于STL库中的红黑树,一般不会直接调用,但是set、map都会以它为基础建立

分配器也是STL库容器中默认会调用来分配内存/释放内存用的,当然也可以自己调用

它和malloc的区别?感觉都是分配内存用的

分配器可以构建对象,malloc还需要调用其他方式来构建

这一条内容主要是讲分配子不能做的事情:(如果不自己构建分配子,实际上这条是无用的)
1、分配子是一个模板,需要指定模板参数T
2、提供类型定义pointer和reference,但是始终有pointer为T*,reference为T&

template <typename T>
struct std::allocator {
    typedef T* pointer;
    typedef T& reference;
    // ...
};

3、通常分配子不应该有非静态的数据成员,不要让它根据对象变换状态
4、一定提供嵌套的rebind模板,因为标准容器依赖该模板
5、传给分配子的allocate成员函数是要求内存的成员个数,而不是所需字节数

为什么以对象个数而不是字节数来分配?
类型安全:
通过以对象个数为单位来分配内存,你明确了分配内存的用途,即用来存储多少个特定类型的对象。这增加了类型安全性,因为编译器可以更容易地检查和推断代码的正确性。

内存对齐:
不同类型的对象可能有不同的对齐要求。以对象个数为单位分配内存,分配器能够确保所分配的内存满足对象的对齐要求。如果你按字节分配,可能会导致对齐问题,进而引发未定义行为。

与对象构造和析构的集成:
分配器不仅仅用于分配内存,还与对象的构造和析构紧密结合。按对象个数分配内存,更符合 C++ 对象模型的设计理念,使得在分配内存后更容易构造对象并在适当时析构对象。

第十一条、理解自定义分配子的合理用法

也就是自定义分配子能做什么

需要自定义分配子的情况:
1、STL默认的内存管理器太慢或太浪费内存;
2、不考虑多线程,仅使用单线程,希望节省开销;
3、希望能够在一个堆中共享容器
4、…

一般不建议自己定义,但是一定要定义的话,遵循规则:同一类型的分配子必须是等价的

也就是说,分配子要对类操作,而不是对象

第十二条、切勿对STL容器的线程安全性有不切实际的依赖

举例:多个线程对一个vector写数据,会安全吗?

不一定,实际上STL库最多能做到:

多个线程读一个容器
多个线程对不同容器写

这也是最多能做到的,也就是说这两条都不一定能保证

比如:

vector<int> test;
... // push elements into vector
for(int ele : test) if(ele == 5) ele = 0;

在这个线程中我们搜索vector中等于5的元素,并把它改成0,那如果另一个线程也在对这个vector进行写操作呢?

我们是没法指望vector自己检测出来这种情况,并且在搜索时把vector锁住

那么就只能自己锁住了

vector<int> test;
... // push elements into vector
getMutex(test); // lock vector
for(int ele : test) if(ele == 5) ele = 0;
releaseMutex(test); //unlock vector

所以在多线程中使用STL时需要注意这种问题,该写锁就写锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值