More Exceptional C++ 读书笔记
Item 1 Switching Streams
(1): 多考虑易读性,避免编写过渡简洁但是不易懂,不易维护的代码;
记个相当简洁的流切换代码:
(argc > 2 ? ofstream(argv[2], ios::out|ios::binary) : cout ) << (argc > 1 ? ifstream(argv[1], ios::in|ios::binary) : cin).rdbuf();
(2): 设计函数时
a: 优先考虑可扩展性,但对于一个简单问题的处理不应当采用过渡设计和过于专一的发放,要权衡这两者;
b: 多考虑封装,将问题分离开;只要可能,就应当让一段代码只拥有一个功能;
Item 2Predicates, Part 1: What remove() Removes;
(1): remove()删除在[first,last)范围内的所有value 实例remove()以及remove_if()并不真正地
把匹配到的元素从容器中清除即容器的大小保留不变而是每个不匹配的元素依次被赋
值给从first 开始的下一个空闲位置上返问的ForwardIterator 标记了新的元素范围的下一个
位置例如考虑序列{0,1,0,2,0,3,0,4} 假设我们希望删除所有的0 则结果序列是
{1,2,3,4,0,3,0,4} 1 被拷贝到第一个位置上2 被拷贝到第二个位置上3 被拷贝到第三个位
置上4 被拷贝到第四个位置上返回的ForwardIterator 指向第五个位置上的0 典型的做法
是该iterator 接着被传递给erase() 以便删除无效的元素内置数组不适合于使用remove()
和remove_if()算法因为它们不能很容易地被改变大小由于这个原因对于数组而言
remove_copy()和remove_copy_if()是更受欢迎的算法.
(2): 一个remove_nth的正确实现:
template<FwdIter>
FwdIter remove_nth(FwdIter first, FwdIter last, size_t n)
{
assert(distance(first, last) >= n);
advance(first, n);
if(first != last)
{
FwdIter dest = first;
return copy(++first, last, dest);
}
return last;
}
(3): 多考虑泛型算法 + 断言的形式,而不是重新实现一个泛型算法;
(4): 所谓断言是一个函数指针或者函数对象, 返回一个bool,泛型算法依据返回值执行相应操作;
(5): stateful 断言;不依赖泛型算法传递对象到operator(),而是根据调用次数独立的自动更新函stateful断言内的数据;
(6): stateful断言对泛型算法的要求:
a: 泛型算法必须用一个恒定的函数对象,而不能用其拷贝来作运算;
b: 泛型算法必须把函数对象按照已知的顺序提交给每一个容器中的元素;
c: 一个安全的stateful断言:
template<class T>
class CountedPtr
{
private:
class Impl
{
public:
Impl(T* pp) : p(pp), refs(1) {}
~Impl() { delete p; }
T* p;
size_t refs;
};
Impl *impl_;
public:
explicit CountedPtr(T *p) : impl_(new Impl(p) ) {}
~CountedPtr() { Decrement(); }
CountedPtr(const CountedPtr &other) : impl_(other.impl_) { Increment(); }
CountedPtr& operator=(const CountedPtr& othher)
{
if(impl_ != other.impl_)
{
Decrement();
impl_ = other.impl_;
Increment();
}
return *this;
}
T* operator->() const { return impl_->p;}
T& operator*() const { return *(impl_->p); }
private:
void Dcrement()
{
if(--(impl_->refs) == 0)
delete impl_;
}
void Increment() { ++(impl_->refs); }
};
class FlagNthImpl
{
public:
FlagNthImpl(size_t nn) : i(0), n(nn) {}
size_t i;
const size_t n;
};
class FlagNth
{
public:
FlagNth(size_t n) : pimpl_(new FlagNthImpl(n)) {}
template<typename T>
bool operator() (const T&)
{
return ++(pimpl_->i) == pimpl_->n;
}
private:
CountedPtr<FlagNthImpl> pimpl_;
};
Item 4 Extensible Template: Via Inheritance or Traits ?
(1): 什么是Traits class
一个封装了一组类型和函数供模板类或者模板函数对某个被操作对象做偏特化的类;
(2): 限制类
例如判定某个类里面是否存在某个函数;
可以
template<class T>
class HasClone
{
public:
static void Constraints()
{
T* (T::*test)() const = &T::Clone;
test;
}
HasClone() { void (*p)() = Constraints; }
};
template<class D, class B>
class IsDerivedFrom
{
class No { };
class Yes { No no[2]; };
static Yes Test(B*);
static No Test(...);
static void Constraints(D *pd) { B *pb = pd; pb = pd; }
public:
enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) };
IsDerivedFrom() { void (*test)(D*) = Constraints; }
};
如果D是派生自B, 那么这个类的Is值为sizeof(Yes),否则为 sizeof(No);如果class XX : public IsDerivedFrom<B, D>的话,如果
D派生自B,那么将编译通过,否则无法编译通过;
另外可以用这个类的Is来当作模板偏特化的参数,来给某个模板类两个版本,例如
template<class T, size_t>
class X {};
template<>
class X<T, 0> {};
template<>
class X<T, 1> {};
void f()
{
X<T, IsDerivedFrom<D, B>::Is> xx;
}
(3): 关于如何实现Traits类:
a:首先设计一个常规的Traits
例如:
template<class T>
class Traits
{
public:
static T* Clone(const T* p) { return new T(*p); }
};
b: 然后是对某个类进行偏特化
template<>
class Traits<MyCloneable>
{
public:
static MyCloneable* Clone(const MyCloneable* p) { return p->Clone(); }
};
Item 5 typename
(1): 在一个模板声明或者定义中,使用依赖于模板参数类型的类型名称,那么它不被推导为一个类型名,除非在这个名称的前面声明 typename关键字;
Item 6 Containers, Pointers, and Contrainers That Aren't
(1): 如果一个conforming sequence,例如vector<char>,提供了operator[](),这个操作肯定返回一个左值char, 通过这个可以得到确 切的元素地址
(2): 如果表达式 &t[0]有效,那么T::operator[]()必须存在且返回的对象必须存在operator&(),且此对象的operator&必须是有效的 value_type*或者等等价于value_type*的对象;也就是说首先容器的operator[]()必须返回容器内部元素的引用;
一般来说,对STL容器的迭代器作&*t.begin()操作是有效地,因为对迭代器做解引用可以返回一个容器内部元素的引用;
(3)上述除了在std::vector<bool>时无效外,其他都成立;因为std::vector<bool>即不是个容器,也不是个序列,它只是一个vector的偏 特化,需要vector<bool>时,请使用std::bitset代替;
(4): a: 不要过早优化,
b: 如果你不清楚是否需要优化,那么不要优化;
c: 如果不清楚什么地方需要优化,那么不要优化;
Item 7 Using vector and deque;
(1): 用std::vector<T>代替传统的 array T ta[100];
some_api(T *ta, size_t size);
vector<T> vt(100);
some_api(&vt[0], vt.size());
(2): 大多数情况下,请多考虑std::vector,而不是deque; 两者主要的不同在于其内部的数据存储方式,deque的存储方式为chunk,有一个固 定的元素数量,故此,在deque的任意一段都能有效地插入元素, 而,vector的存储分配方式为一段连续的内存,故此只有在后端插入元素 才是有效的
deque在某些方面更容易使用,更易于容器容量的增长, deque也没有reserve()和capacity(),deque同时也不需要他们,vector在大量 的元素push_back之前先reserve()出来一定的空间可以大量的减轻内部的分配操作,
(3): vector无法缩减已经申请的内存容量,reserve()只能增长,而不能缩减,所以;
template<class T>
void shrink_to_fit(vector<T> &vt)
{
vector<T> temp(vt.begin(), vt.end());
temp.swap(vt);
}
template<class T>
void Clear_mem(vector<T> &vt)
{
vector<T> temp;
temp.swap(vt);
}
Item 8: Using set and map
(1): 关联容器的维护靠的是一个默认为operator<()的KEY的操作符,所以,Key是不可以更改的,所谓的map<int, string>实际上每个节点维 护的也是一个pair<const int, std::string>,如果强制通过iterator 取得Key的引用,并且通过const_cast强制变更Key值的话, 会出现未定义的错误,决不要这么做;同理,set可以想象为map<Key, void> ,最好不要通过set<T>::iterator来修改T的值;
Item 9: Equivalent Code
(1): 避免使用宏,通常宏会导致代码非常的难以理解,因此难于维护;
Item 10 Template Specialization and Overloading
略;
Item 11: Mastermind
(1): 注意复用标准库代码,例子代码略;
Item 12 inline
(1): inline函数可能带来的影响:
a: 程序大小;
b: Memory footprint
c: 执行时间
d: build time;
(2): 第一是不要做优化,第二条优化准则也是别作优化; 第三是如果必须作优化,那么直到真正的知道需要做什么优化时候再作优化;
Item 13 - 16 Lazy Optimization
(1): 缓存增长策略;
a: 精确增长: 优点是不浪费空间,缺点是缓慢的性能;
b: 固定增长: 相对于a,少浪费了一点空间,缺点为中等的性能损失;
c: 指数增长: 优点是优异的性能,缺点是浪费一点空间;
(2): Copy-on-write: 字符串拷贝之后,再修改某一个内部内容之前,不必为每一个对象分配新的buf,可以让他们共享同一个buf,并添加一 个引用计数
(3): 代码略;
(4): 多线程部分参见源码就可以了; copy-on-write的多线程部分将导致很严重的性能损失;
Item17 Constructor Failure, Part1 Object Lifetimes;//我认为很重要的东西,在这两本书中最重要的东西了;
(1): 函数级异常处理
(2): 对象生命期:一个对象的存在,是从constructor调用完成,并成功返回之后,一直到这个对象的destructor被调用那之时;
(3): 如果从一个constructor抛出异常,那么这个对象constructe失败,也就是说(重点),这个对象从来就没存在过,所以,当
constructor 抛出异常时, 这个建构失败的对象的destructor决不会被调用,而已经建构的类成员的destructor将被依次调用
(4): constructor函数级异常处理只能转换从函数内传出的异常,而不能压制这种异常处理的抛出,只能重新抛出;constructor函数没有什么 可返回的,并且考虑到对象生命期,故此,constructor函数级异常处理必须以重新抛出原来的异常或者抛出新的异常结束;
标准定义为: 如果任何基类construcotor或者成员constructor抛出异常,那么这个对象的constructor就已经失败,对象生命期从未开 始,故此,constructor的异常不可能被constructor级异常处理吸收,只能重新抛出;
constructor抛出的异常不能是基类或者成员的引用,因为在异常抛出时,类成员和基类已经被销毁或者是未定义的状态,故此,绝不能抛 出基类或者类成员的引用或者指针;而new出来的指针等,应当用auto_ptr包装(RAII)或者在constructor内进行清理;
结论: a: constructor异常处理只能作为某种信息记录或者异常转换处理重新抛出
b: destructor级异常处理没有实际用处,因为destructor决不应当抛出异常;
c: 所有其他函数级异常处理也没有实际用处,因为当执行到函数级异常处理的catch块,则函数本身内在的所有数据已经没有 了;
d: 执行自愿申请的操作请在constructor函数体内执行,避免在初始化器列表中,这样当抛出异常后,可以在constructor内 部的catch块内进行释放;
e: 当异常出现时,所有申请的资源应当在constructor内的catch块内释放;
f: constructor的可抛出的异常列表应当允许所有的可抛出异常,例如基类异常或者成员的异常等;
g: 用Pimpl方法容纳类的可选部分;
h: 请多考虑RAII方法
Item 19 uncaught_exception
(1): 关于uncaught_exception()函数;
当被抛出的对象完成赋值直到生命为匹配被抛出的对象的catch块完成初始化之间,此函数返回true;,这包含堆栈回退,如果异常被 再次抛出,uncaught_exception() 从再抛点到再抛对象被再次捕获间返回true。
(2): 再论destructor不能抛出异常的原因:
class X { ~X() { throw 1; } };
void f()
{
X x;
throw 1; //抛出1的同时,x被析构,等用两个异常被抛出,此时,哼哼,程序异常中止了;
};
所以,再写destructor时侯,请声明throw();
如果destructor调用某个函数可能抛出异常,那么请用try/catch块包裹此函数,以防止异常从destructor抛出;
(3): uncaught_exception()没什么用,所以,别用它~~
Item 20, 21 : An Unmanaged Pointer Problem
(1): 在单一的表达式内执行每一个独立的资源分配,,然后立即给新申请的资源一个管理对象,例如针对new auto_ptr;
Item 22, 23, Exception-Safe class Design
(1): 异常安全级别-->参见exceptional C++
a: 基本安全;
b: 强保证异常安全;
c: 不抛出异常;
(2): 拷贝赋值的强异常安全规范
a: 先生成临时对象;
b: 然后用一个决不抛出异常的Swap函数和临时对象交换
(3): 用Pimpl方法来提供拷贝赋值的强异常安全
核心思想就是将可抛出异常的数据成员放到Pimpl内,当临时对象赋值结束后,则用Swap置换
(4): a: 异常安全处理应当影响类的设计;
b: 用Pimpl来实现异常安全;
c: 明智的使用Pimpl方法;
d: Pointers 是你的敌人,但是auto_ptr可以对付它;pointer是你的朋友,因为pointer上面的操作不会抛出异常;
(5): 多用聚合,最好不要用派生,以保证异常安全以及低耦合;
Item 24 Why multiple Inheritance?
(1): 避免从一多于一个的非协议类实现多继承(协议类为抽象类,不包含数据成员,完全的由纯虚函数组成);
Item 25, 26 有关MI的暂时略;
Item 27:(IM) Pure Virtual Functions
(1): 关于为什么给默认的纯虚函数定义函数体;
答: a: 在派生类中可以使用;
b: 提供一部分函数功能,另一部分可以在派生类中实现;
c: 提供编译时诊断信息;
Item 28 : Controlled Polymorphism
(1): class B { public : virtual void test(); };
class D : public B { public: void test(); };
void test1(const B& b) {...}
void f1() //如何让这个函数中的test无法接受D的对象;
{
D d;
test1(d);
}
class D : private B { public: void test(); friend f1(); };
Item 29 : Using auto_ptr;
(1): 如果要new 出来一个数组,那么请用
auto_ptr<vector<T> > pvt(new vector<T>(20));
Item 30, 31 Smart Pointer Members
(1): 实现一个可作为值语义的auto_ptr
template<class T>
class VPTraits
{
public:
static T* Clone(const T* p) { return new T(*p); }
};
template<class T, class Traits = VPTraits<T> >
class ValuePtr
{
public:
explicit ValuePtr(T *p = 0) : p_(p) {}
~ValuePtr() { delete p_; }
T& operator*() const { return *p_; }
T* operator->() const { return p_; }
void Swap(ValuePtr &other) { std::swap(p_, other.p_); }
ValuePtr(const ValuePtr &other) : p_(CreateFrom(other.p_)) {}
ValuePtr& operator=(const ValuePtr& other)
{
ValuePtr temp(other);
Swap(temp);
return *this;
}
template<typename C>
ValuePtr(const ValuePtr<C> &other) : p_(CreateFrom(other.p_)) {}
template<typename C>
ValuePtr& operator= (const ValuePtr<C> &other)
{
ValuePtr temp(other);
Swap(temp);
return *this;
}
private:
template<class C>
T* CreateFrom(const C *p) const
{
return p != 0 ? Traits::Clone(p) : 0;
}
template<class C, class Traits> friend class ValuePtr;
T *p_;
};
Item 32: Recursive Declarations
(1): 如何实现一个返回自身函数指针的函数;
class FuncPtr_;
typedef FuncPtr_ (*FuncPtr)();
class FuncPtr_
{
public:
FuncPtr_(FuncPtr p) : p_(p) {}
operatorFuncPtr() { return p_; }
private:
FuncPtr p_;
};
FuncPtr_ f() { return f; }
int main()
{
FuncPtr p = f();
p();
}
Item 33: Simulating Nested Functions
(1): 哈哈,才发现啊才发现,原来函数内部也能定义类,C++也太酷了吧~~~~~
void f()
{
class X
{
public:
void operator()(int i){ cout << i << endl;}
};
X x;
x(3);
};
class X
{
public:
void operator()(){ cout << "aaa" << endl; }
};
int main()
{
f();
X x;
x(3);
}
(2): 注意,函数内不能定义类模板,本地类也不能当作模板参数,因为没有连接性;
(3): 嵌套类和函数内的本地类主要应用于实现的隐藏和依赖性管理;
(4): 这东西其实没什么大用;
Item 34: Preprocessor Macros
(1): 略;
Item 35: #definition
略;
Item 36 : INITIALIZATION
(1):
class S
{
public:
S(int i) : i_(i) {}
private:
int i_;
};
S s[3] = {1, 2, 3};
这样子竟然可以,晕
Item 37 Forward Declarations
略;
Item 38 typedef
略;
Item 39, 40 NameSpaces
(1): a: 决不能直接写using NAMESPACE::xxx在头文件中;
b: 决不能写using namespace NAMESPACE 在头文件的文件范围内;
c: 在实现文件(.cpp)的#include之前,决不要写using;d: 用新的#include<cheader>风格代替旧的C风格#include<header.h>