C++ Primer - Summary

C++ Primer Summary

Posted by Becks on June 18, 2018

  • function return reference 可以用non-reference 去接, non-reference 接的话借的是copy
  • namespace 中function 不是在class 但是声明了 static 是 give internal linkage, meaning that it is only accessible within the translation unit that contains the definition. Without static, it has external linkage, and is accessible in any translation unit. So you’d use static (or, alternatively, an unnamed namespace) when writing a function that’s only intended for use within this unit; the internal linkage means that other units can define different functions with the same name without causing naming conflicts.

iteral type: (int, int pointer, int reference, double, enum, char, char array char pointer) scalar type, reference type, an array of literal type; 注:string 不是literal type

A class is a literal type

  • has a trivial destructor(自己没有定义destructor 并且 non-static member object 也没有定义destructor )
  • every constructor call and any non-static data member that has brace- or equal- initializers is a constant expression, int i = 4; int j {5};
  • is an aggregate type, or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
  • has all non-static data members and base classes of literal types
struct B { ~B() {} };//not literal type

class X {
  int i = 4;
  int j {5};
public:
  X(int a) : i{a} {}  //initializes with a and 5
  
  X() = default;      //initializes with 4 and 5
  
};

陷阱: 1. constexpr, type Aliases under 2. Variable and Basic Types 2. Array: Pointer Arithmetic: 可以负数 3. Strings, Vectors, and Arrays. 4. Function only with top level and return difference error. 5. Function name lookup before type checking 6. Functions. d 6.Function Names resolve and calculate 6. Functions. d 7. function pointer parameter 和return type的转换6. Functions. d 8. *this is reference to object (not const reference) 7. const class object 不能call non const member function 8.const function 对于 const function is better match (因为不用转化lower const) 9.const function 只能返回 const reference (buneng) 10. const function 内不能call不是const的function. nonconst function 可以call const function. 11. constructor initializer list order 不会影响他们实际初始化的顺序. 11 static member可以用于default parameter


8.IO Library

  • 为支持wider character, library 有 wchar_t, 有wcin, wcout, wcerr, 也有wifstream
  • ifstream, istringstream 继承自 istream
  • No copy or assign for IO Object: 通过reference pass 给 function(注意不能是const, 因为改变state>), return must be reference.
  • state
    • badbit: not possible to use when badbit set. s.bad() return true if badbit set
    • failbit: recoverable 比如expected numeric when meet char. s.failbit() return true if badbit and failbit set
    • goodbit: guaranteed to have value 0 表示no failure
    • rdstate(): current state, s.setstate(flag): reset condition of s to flags
  • 每一个流都管理一个缓冲区(buffer), 用来hold data that program reads and writes.
  • Using a buffer 可以combine serval output operations into a single system-level write. 因为writing to device time-consuming. combine 多个到single write 可以 performance boost
  • Buffers Are not flushed if program crashes. 如果程序异常终止, 缓冲区不会刷新, 当一个程序崩溃(crash)后, 它输出的数据可能停留在输出缓冲区中等待打印. (所以debug 时候小心)
  • 有几种条件导致换种刷新:
    • program completed normally. buffers flushed as part of main
    • buffer become full
    • flush buffer explicitly using manipulator endl, ends, flush
    • unitbuf manipulator: cout << unitbuf; 所有输出操作后都会立即刷新缓冲区. cout << nounitbuf; // returns to normal buffering
    • Output stream might be tie to another stream. 当绑定的stream read or write, 会flush. 默认是cincerrr 绑定到cout, reading to cin or writing to cerr flushes the buffer in cout.
      • tie: 一个版本是接受个pointer, cin.tie(&cout);
      • tie: 另一个版本是返回现在绑定的stream ostream* old = cin.tie(nullptr);
  • when we creat a file stream, 如果提供file name, open is called automatically.
  • When an fstream object is destroyed, close is called automatically
  • fstream constructor explicit
  • when specify ofstream, 没有specifiy, out and trunc implicitly. 只specify out, trunc implicitly. 只specify trunc, out implicitly
  • when specify app on ofstream, out implicitly
  • app seek to the end before every write 每次写操作前均定位到文件末尾
  • ate Seek to the end immediately after the open 打开文件后立即到文件末尾
  • stringstream constructor is explicit, holds an copy of stream sstream strm(s)
  • getline是从一个ifstream中读取数据. getline 不会跳过空格, 如果读取的string 第一个是空格,会保留空格

9.Sequential Container

  • list /forward 不支持random access, 花销大
  • forward_list no size operation, no back, 但有end
  • STL Array support copy constructor/assignment, brace assignment/initilization, 但没有member 的 begin end, no iterator constructor, 但可以用 array<int,10>a; begin(a); end(a);
  • STL Array 不为空, default initialzer member是class, 必须有default constructor
  • assignment operator invalidate iterators, but swap not (internal structure swap)
  • Assign 可以用于different but compatible type
  • iterator constructor 不需要type exact match 比如double 到int
  • const object 的iterator, is const iterator
  • deque add / remove element 从两边只invalidate iterator, 不invalidate reference
  • vector/string add invalidate all, delete 对删除之前的没有影响
  • capacity (size before allocate) vs size 区别
  • const char * -> string 必须null terminated
  • insert 在p前插入, 返回插入第一个元素, erase 返回删除后一个元素的

10: Generic Algorithms

  • Algorithm never execute container operations, 不会改变size, 但是会add / remove elements using inserters
  • Algorithm not check write operations: 假设destionation 足够大hold elements, 如果空间不够, undefined behavior. 确保有足够空间用inserter
  • equal 比较两个sequences hold the same value. 可以是不同容器, 一个是vector, 一个是list
  • accumulate 可以用 string("") 作为第三个参数, 但不能用 "" 因为no accumlate for const char*, 因为char pointer 没有定义 +
  • unique 因为不能改变容器大小, 返回an iterator that denotes the end of the range of the unique value
  • predicate can be returns a value used as condition
  • callable object: e(args)
  • lambda function: [capture list] (parameter list) -> return type { function body }
    • capture list capture local nonstatic varaibles 比如 cout 无需 capture
    • 可以省略 parameter list or return type 或两个都省略
    • 不能有parameters has default value
  • lambda capture by value: 捕获指 是 创建时 而不是调用时的值, 随后修改被调用没有影响
    • 如果想改变 capture value 的值, 加mutable, 有mutable 不能省略 parameter list. 但可以省略return type
    • 若value is pointer, lambda copy 值和 外面的 值 指向一个object, object 改变, value的pointer object 值也变, 要确保pointer valid when 执行lambda function
  • lambda capture by reference [&v1], 必须确refereneced object 存在 当执行lambda function 时候
    • capture by reference 是否可以改变取决于 object 是不是const
  • Implicit capture: 让compiler 自己infer capture 的值, by value [=], by reference [&]
  • Mix: Implicit 必须放前面, explicit 放后面, 如果第一个是by value, 第二个必须是by reference [=,&c]; 如果第一个是by reference, 第二个必须是by value[&,c]. 比如
  • 尽量减少reference, pointer capture
  • bind: auto newCallable = bind(callable, arg_list);,
    • arg_list 可以有placeholder _1, _2 表示给newCallable 第一/二个参数, 可以rearrange, _2_1 前面
    • using declaration is using std::placeholders::_1
    • 可以bind 传 reference. 用ref, 比如IO类
    • 可以用bind 就根据一个predicate 同时来正向反向 sort, 方法是交换_1_2 的位置
  • Insert Iterator: takes a container yields a iterator, 当像iterator 赋值, iterator call container operations to add elements. it=t call 相应的push, insert, *it, ++it, it++没有实际意义, 有back_inserter(call push_back), front_inserter (call push_front),还有inserter
    • auto it = back_inserter(vec);, it=42*it=42是一样的
  • type use istream_iterator reads 必须有 >> input operator defined. ostream_iterator 使用对象必须有 << defined
  • istream_iterator lazy initialization: delay 读取, construct 不读取,只有*it 才读取
    • istream_iterator默认值是off-the-end value,ostream_iterator 不存在off-the-end iterator
    • ofstream_iterator++ 是 read next value from input stream ofstream_iterator++ 没有实际作用
    • 例子 读取到vector, stream_iterator<int>in_iter(cin),eof;vector<int> vec(in_iter, eof);
  • reverse_iterator ++it移动到上一个, --it 移动到下一个
    • 获取reverse iterator rbegin, rend, crbegin(最后一个element位置), and crend(是在begin开始前的iterator)
    • base 是 next element to the reverse_iteartor pointing to
  • Forward Iterator 跟 Birectional iterator 比 只多了 decrement(反向), 两个都是可以read and write, multi-pass, increment
  • Input, output iterator 是 single pass 的, 用于比如find, accumulate, istream_itearator
  • Random-access iterators: 需要const-time access to any position, 像pointer 支持subscript operator: 跟forward, birectional iterator 比多 subtraction operator, 生成distance betweeen operators
  • 优先考虑 list 和 forward_list 自己的算法

12.Dynamic Memory

  • automatic objects: create and destoryed in block when definition is entered and exit
  • static objects: allocated before first use and destoryed when program ends
  • dynamically allocated object: independent of where created, exist 直到explicitly freeded
  • static memory: local static object(存在until 程序结束)
  • stack memory: nonstatic objects defined inside function. stack objects 存在only when program execute. FIFO
    • stack usually in CPU cache, operations faster
    • limited resources, running out of stack is stack overflow, 一般不会有这个问题, 除非crazy recursion
  • heap or free store: dynamically allocated object: allocates at run-time
    • 必须explicitly destroy such objects
    • Dynamic memory is problematic:
      • 忘记删除, memory leak
      • 用了删除的, pointer invalid
    • running out of heap result in bad_alloc
      • 可以用 nothrow 阻止 throw bad_alloc
      • int *p2 = new (nothrow) int;: 如果不能分配内存, 返回空指针
  • new and delete: error-prone
    • newallocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object;
    • delete p: destroys that object, and frees the associated memory.
      • p 必须point to dynamically allocated object or null
      • delete a pointer not allocated by new or delete the same pointer value more than once -> undefined
      • compiler 不能告诉是staticly or dynamically allocated object
      • compiler cannot tell 是否 memory addressed by pointer 是否删除了.
      • make the pointer null after delete, 看memory 是否被删除. delete p;, p = nullptr;
        • 删除后的pointer 又被叫做 dangling pointer. dangling pointer has all problem as uninitialized pointer. by set as nullptr: 知道不指向任何对象
  • smart pointer:
    • if we do not initialize a smart pointer, it is initialized as a null pointer
      • shared_ptr<double> p1;
    • 不要pass shared pointer by value to function
      • 比如 process(shared_ptr<int>(x)), x可能被destoryed after function call
    • smart pointer 确保memory is freeded 当 block 有exception exit prematurely, 如果built-in pointer 有error between new and delete, memory not freeded
    • not use built-in pointer to initialize more than one smart pointer
    • dangerous to use built-in pointer to access an object owned by smart pointer, 因为不知道什么object is destroyed
    • delete get() 返回的指针
    • 不把 get() 返回指针跟其他smart pointer 绑定
    • 如果用 get() 返回pointer, remember pointer become invalid when 最后smart pointer goes away
    • 不support pointer arthimetic, 必须用 get() 获取built-in pointer 做 pointer arithmetic
  • shared_ptr:
    • constructor that takes a smart pointer 是 explicit的
      • pointer used to create smart pointer 必须point to dynamic memory, 因为 smart pointer use delete to free associated object
    • 如果constructor 是 unique_ptr, make unique_ptr null, shared_ptr<T> p (u)
    • cannot implicitly convert a built-in pointer to a smart pointer
    • p = q: 递增 q 的reference count, 递减 p的, delete p’s existing memory if p’s count goes to 0
      • destructor decrease reference count, 如果是0,销毁对象, 内容被释放
    • p->unique: 若 p.use_count 为 1, return true, 其他false
    • p.use_count: 返回与p 共享对象的智能指针数量, 可能很慢, intended primarily for debugging purposes
    • make_shared: safest way and returns a shared_ptr that poitns to that object
      • 可以用 auto save make_shared result auto p6 = make_shared<vector<string>>();
  • unique_ptr
    • 没有像shared_ptrmake_shared function
    • 不能copy constructed
    • 不能copy,可以调用 release()(切断unique_ptr和它原来管理的对象联系) or reset() (delete pointed object)
      • release 返回的通常用来initialize or assign 另一个smart pointer
    • 可以 copy or assign a unique_ptr that is about to be destoryed. 常见例子是 return a unique_ptr from a function.
    • 与 shared_ptr, 必须give deleter type inside angle bracket for unique_ptr
  • weak_ptr:
    • doesn’t control lifetime of the object, points to an object that is managed by a shared_ptr. 当最后一个shared_ptr 被删除, 对象被deleted, even if weak_ptrs point to
    • 创建 weak_ptr 需要 shared_ptr 初始化, 不increase shared_ptr reference count
    • w.lock(): checks 是否 weak_ptr 指的对象仍然存在, 如果存在, returns a shared_ptr to shared object, otherwise return nullptr
  • dynamically allocated object:
    • object allocated on heap is unamed. new returns a pointer to the object it allocates
    • object allocated 是default initialized, built-in or compound type 有undefined value, objects of class type 是initialized by default constructor
    • 初始化可以用 (), {}, value initialization 是 () type name + 空括号
      • 如果 initializer element 大于 size, new throw bad_array_new_length
      • 可以put element in {}, 但是不能put in (), 不能用 auto 放在new 后面 to allocate an array 比如 new auto
    • 可以用 auto deduce type
    • const object: A dynamically allocated const object must be initialized
      • the pointer return from new is a pointer to const
      • a const dynamic object of class type 定义了 default constructor 可以被 implicitly initialized, 其他对象不行 比如 string *pcs = new const string;
  • dynamically allocated array:
    • shared_ptr : 必须provide deleter 但不用specify deleter type, 否则删除就是 array 第一个元素
      • shared_ptr<int>sp(new int[10], [](int *p){delete[] p;});
    • 当unique_ptr points to dynamic allocated array
      • deleter 需要 deleter type in bracket <>
      • 如果是built-in type, 需要加上括号<int[]>, 说明指向 dynamic array of int not an int, 就不需要specify deleter, shared_ptr pointer type 不能是<int[]>
      • 可以用subscript operator to access dynamically allocated array
    • default initialize int *pia2 = new int[10]();. 10个值初始化为0的int.
    • most application should use library container 而不是 dynamically allocated array.
    • new 返回不是 array type. 是 pointer to element type of array.
    • dynamically allocated array 不能用 begin and end, 因为返回时 pointer. 也不能用 range for loop
    • 可以 allocate empty arrray, 因为pointer cannot be dereferenced, after all, it points to no element
    • delete []:elements are destoryed in revere order: 最后一个element 先被删除:
      • 如果不加上 []: 可能misbehave without warning during execution
  • Allocator: separate allocation from construction
    • it provide raw, unconstructred memory.
    • error to use raw memory which object not constructed.
    • 只能 destroy elements that are actually constructed.
    • a.deallocate(p, n) n 需要 the same size call to allocate. 如果 size 少了, 不会报错, 内存泄漏
    • uninitialized_copy, uninitialized_fill 批量 construct ***

13.Copy Control

  • copy constructor:
    • parameter reference to const, 必须是reference, 否则无限recursion.
    • 通常不是explicit
    • 只copy nonstatic member, type 决定如何copy,
      • class type: copy constructor
      • 尽管array 不能copy, synthesize copy constructor copy each elements from array. 如果array element is class, 用class copy constructor
    • insert push copy initialization, emplace direct initialization
    • 有explicit, 只能direct initialization(vector<int>a(10)), no copy initialization
    • during copy initialization, compiler 可能忽略 copy/move constructor and create object directly. 但是copy/move constructor 必须 accessible (not private)
  • assignment operator: return reference, library container / built-in type return as reference. 若return value, 还需要call copy constructor
    • bad example 是: 直接delete exisiting left-hand operand, 导致如果是self-assignment, access an invalid pointer to make copy
    • 顺序方法一:
      1. copy right-hand to local temporary
      2. destory existing member of left-hand operand
      3. copy from temporary to left
  • destructor: free resources and destroyed non-static data member
    • 先run destructor function body then class members are destoryed(member destructor), reversed order of initialization
    • destructor not run when reference or pointer out of scope
    • 会synthesize destructor for any class, 只有当base destructor 是private, 才不会生成
    • 如果 destructor 是 =delete 不能create an object, 不能delete object or delete pointer to dynamically allocated object
  • 当 specify =default inside class, implicitly inline,如果不想让它inline, 可以specify =default on members definition outside class
    • can only use =default on synthesize member version
  • =default 不像 =default: must appear on the first declaration of a deleted function
    • 不像=default, =delete 可以用于任何 function
  • private copy control: user cannot copy but friend and member can still copy.
    • 阻止friend / member copy, not define them,
    • user error in compiler time, friend/member error in link time.
  • Swap:
    • 通常involve one copy and two assignment operator
    • 如果class 定义自己的 swap, algorithm use class-version, 否则 use swap defined by library
    • usually define as friend member to access private data (argument-dependent lookup )
    • optimize by define inline
    • 正确call 方法 using std::swap; swap(a,b): 只有当member 没有 swap 定义时, 才会call library version
      • 根据template function matching rule: class member 更specialized
    • class 定义 swap 通常用于 assignment operation: copy and swap
      • copy 可以通过 parameter copy by value, call copy constructor to 完成复制, 而不用copy by reference
      • assignment use copy and swap are exception safe and correctly
  • rvalue reference:
    • rvalue reference <- rvalue 只能绑定rvalue (除了template, auto), 也可以绑定move 返回的 rvalue reference,
    • nonconst lvalue reference 不能绑定 rvalue, 也不能绑定move() 返回 的 rvalue reference, const lvalue reference 可以绑定rvalue
    • all variable are lvalues even type is rvalue reference
    • move 不用provide std::move 因为argument dependent lookup
      • performance boost
      • 用于不能copy的, unique_ptr or IO classes
  • move constructor: first parameter is rvalue reference, 任何其他的parameters must all have default arguments;
    • must ensure moved-from object is harmless after move
    • not throw except (因为no resouse allocation) noexcept
      • 在所有的declaration and definition
    • library 的move, 如果发生异常, 比如比如vector push_back 有exception, vector itself 是unchanged
  • move assignment:it is crucial to , should maredt as noexcept
    • After move operation, “moved-from” object must remain a valid, destructible object but no assumptions about its value.(可以assign new value to moved from object)
    • if class no move operation(也没有synthesized的), copy 代替move operation (当copy parameter type is const reference )
    • 如果没有copy, 不能用move constructor 代替copy, 因为不能用rvalue reference to take lvalue
  • 如果copy/move constructor 都avaiable 根据function matching rule see which one is better
    • move constructor and move assignment operator, 通常是rvalue reference, not const rvalue reference(pass rvalue to copy operation, 需要const version not exact match), 否则 const rvalue reference and const lvalue reference 都accept everything. 还因为move operation is to steal from argument
  • move iterator: transform ordinary iterator to move iterator: make_move_iterator
    • make_move_iterator(vec.begin())
    • dereference return rvalue reference
    • library no guarantees about which algorithm use move iterator and which cannot. 只有当确定算法运行完不再用时候, 才pass move iterator
  • can call member regardless of its lvalue or rvalue
  • reference qualifier: replace & or && after parameter list
    • 必须在const 后面
    • overload 要么不用,要用就都用
    • 与noexcept类似, reference qualifier 必须同时出现所有的于function declaration 和 definition 中
    • 可以被overload 基于 reference qualifier: 可以根据 reference qualifier 和 const 来区分一个成员函数的重载版本

记: 1. constructor 2. copy constructor 3. copy assignment operator 4. destructor 5. move constructor 6 move assignment operator

  • destructor 只有 当base destructor 是private / deleted 时候才不会生成
  • constructor synthesize only if no constructor defined by user (注: copy / move constructor 也算 constructor )
  • copy constructor: no 3 ,4 , 5, 6 defined 且member 是可以copy的, ( member 是class type 的 copy constructor accessible)
  • copy assignment operator: no 2, 4, 5, 6 defined, 且member 没有const or reference, member 是可以copy assignment的, ( member 是class type 的 copy assignment operator accessible) .
  • move constructor: no 2, 3, 4, 6 defined 且member 没有const or reference member 是可以move的, ( member 是class type 的 move constructor accessible)
  • move assignment operator: no 2, 3, 4, 5 defined 且member 是可以move assigned 的, ( member 是class type 的 move assigment operator accessible)
  • copy / move only for nonstatic data member
  • 即使声明synthesized as =delete 也算user defined

14: Operator Overloading

  • overloading parameter must at least one is class type
  • assignment, subscript, call(), access arrow, dereference, increment, decrement, compound-assignment+= must be member functions
  • input / output IO must be nonmember (因为如果是class member, left-hand operand是IO, must be IO class, 但是IO不能添加member)
  • input 传入nonconst object, ouput 传入 const object
  • assignment operator 可以把不同的类赋值, 比如赋值initializer_list
  • subscript operator 最好定义 const 和 nonconst version (因为有可能可以改变值)
  • postfix return value and use extra int call prefix p.operator++(); postfix p.operator++(0);
  • prefix 需要检查 increment safe or not, postfix use prefix
  • operator->() 如果不返回pointer, 会持续recursion fetch result 是 pointer type. 当是pointer, 返回&this->operator*()
  • 可以定义多个call operator, 参数类型 数量不同
  • library function objects (like less<key_type>, 比较地址) work for pointers
  • Lambda call operator default const function, 如果定义mutable, 不是const function
  • lambda function, compiler 是 unnamed object of unnamed class 含有 function-call operator, capture by values store value 但caputre by reference 不store
  • 不能把一样名字的function overload(名字一样,signature 不一样的) 放进 function
  • conversion operator operator type() no parameter 和 return type, type 不能是array, void, function type
  • conversion operator should be const 不能改变object
  • conversion operator(只能一步) 但 之前或者之后可以有 build-in conversion, 实现多步转换
  • explicit conversion operator:除了判断条件会implicit转换, 不允许implicit conversion, 只能用static_cast
  • 不要定义mutual converion 比如 A constructor take B, B conversion operator 到 A, 也不要定义到多个算数类型转换 比如 A->int, A->double; int ->A, double -> A,
  • 如果到多个type conversion 都available, 不会有rank 比较, 只有到一个type 的多个conversion 才会进行rank比较

15.Object-Oriented Programming

  • dynamic-binding: decision as to which version to run depends on the type of the argument, 在run time 选择函数版本 (when a virtual member function is called through a reference or a pointer to a base-class type)
  • final, override 在parameter list, const, qualifier reference 之后
  • virtual function in base class 是 implicit virtual in all derived class
  • derived virtual function 跟base 不同signiture, 是不override base的
  • 用scope operator prevent dynamic binding, 如果derived class 应该call base virtual, but fail to provide baseP->Base::fuc(), infinite recursion
  • object access virtual, pointer/reference access nonvirtual bound at compile time, 只有pointer/reference access virtual run time
  • static type vs dynamic type: static type known at compile time. dynamic type know at run time. static type of a pointer or reference to a base class may differ from its dynamic type.
  • 只有reference/pointer call virtual function 才是runtime 决定的, reference/pointer call nonvirtual or object call virtual 都是compile time bound
  • pure virtual function 可以被定义, 但只能定义在class外面
  • user 的 derived-to-base conversion 必须是public inheritance, derived的member and friend 无所谓, derived of derived可以用derived-to-base conversion 必须是public/protected的
  • friend 不是 transitive 和 inheritance 的 (清楚这个概念例子)
  • Derived class中 friend function, access base 的protected (base protected 也是derived protected) 成员能通过 derived-class object access, 不能通过base-class object. A derived to B. B friend is C, C 只能用 B 获取A的protected, public, cannot use A 取获取 A
  • class friend function 可以从class 的 derived class 中获取class member, 但不能获取derived member. A friend is C, A derived to B, C 中可以用 B 获取A的值
  • Name Lookup before type checking
  • 可以用using declaration Derived d; d.Base::memfcn(); change access level (base protected 变成 derived 的 public, base 的被hide了)
  • At Compile time: static type 决定什么member can be used (也是virtual function定义在base 和 derived原因). 如果Derived 有 function fcn 而 Base 中没有, then Base *p = &d; p->fcn();是error的, 因为static search scope, fcn 不在 Base
  • Derived-to-Base conversion 也适用于 smart pointer,
  • 如没有virtual destructor, delete on pointer to base that points to derived object undefined
  • constructor 顺序: base -> derived. Destructor 顺序(reverse order) : derived -> base.
  • base constructor virtual call base version
  • Compiler treat type changes during construction or destruction. 因为base constructor run, derived 未生成, base destructor run, derived 已被摧毁
  • 1. synthesize copy constructor 自动call base defined/sythesized copy constructor. 2. user defined copy constructor 不会自动call base defined / sythesized copy constructor, 而call base default constructor. 若call base copy construction 需要显式call in derivation list (有derived-to-base conversion)
  • 如果default constructor, copy/move constructor, copy/move assignment constructr 在 base class 是deleted/inaccessible, 则相应的 derived class synthesized member is deleted 因为没法call based 的
  • 如果 base destructor deleted,then derived class systhesized default,copy/move constructor is deleted
  • 如果base copy construction 被deleted, base/derived move constructor 是不能生成的
  • base 有destructor, derived/base class 没有move operation(copy operation deprecated)
  • inherit constructor:
    • 可以inherit base constructor, 但不能inherit default, copy, and move constructor, assignment operator 因为compiler 会synthesize 如果不define
    • using declaration for constructor, generate code, using Base::Base 继承base all constructors
      • 不会change access level, derived class default intilizated
      • 不能specify explicit or constexpr, same as base
      • default arguments 会生成多个递减argument constructor
      • 不会继承constructor with the same parameter, derived 代替base的
    • inherit constructor 不算user-defined constructor
  • 可以定义lvalue / rvalue version of clone using reference qualifier.

16.Templates and Generic Programming

  • function template
    • T 可以用作 return type, function parameter type, variable declaration or cast
    • nontype parameter 是 value 而不是 type
  • template declaration and definition in the header file. 因为template generate code when instantiation
  • function can deduce template parameter, but class cannot
  • nontype template parameter 必须是 constant expression (比如enum)
  • class template member function 只有被用到才被实例化
  • 如果return type is template parameter, 定义在class 外面,not in class scope,need to specify if type defined inside class Blob<T>::type
  • inside class scope, 不用specify template parameter, 因为assume we use the same type as member’s instantiation
  • template parameter 会 hides any declaration of that name in outer scope
  • 如果使用default template argument, 用的时候 用Blob<>
  • Compilation Errors Are Mostly Reported during Instantiation
  • 告诉compiler we use type not static member, use typename
  • class(template / nontemplate) 的member template cannot be virtual. class template parameter come first, then member parameter list. Member templates arguments 像function template 一样, 可以被deduces出来
  • explicit instantiation: extern
    • 当compiler 看见 extern 不会generate code
    • extern declaration 必须出现在code 使用实例化之前
    • file 的 .o 文件不会包括 被定义了 extern template classs的instantiation
    • 必须link 有extern class 的定义 的.o file 与 用到 extern template 的 file .o file
    • explicit instantiation definition: compiler instantiates all member of that class. 所以要求types works for every member function
  • function template argument conversion
    • top level const ignore
    • const conversion, 没有lower-const pointer / reference 给有lower-const pointer/reference
    • array/function to pointer type (如果T是reference type const T&, 不会有convert to pointer, 这种属于exact match conversion)
    • 不可以 arithmetic conversion, derived-to-base conversion, and user-defined conversion
    • normal parameter 可以进行conversion
  • template/nontemplate class to template /nontemplate class friendship,只有one to one friendship 且friend class 是template 时候,需要forward declaration of template, 1 v 1比如 class-template, template to template(具有相同的实例化 instance 才是friend)
  • typedef 只能用于instantiated class,不能refer to a template
  • 每一个instantiation 都有自己的 class member 成员, 可以用实例化对象access or scope operator Foo<int>::sta 不可以 Foo::sta
  • template declaration 必须包括 template parameters
  • Implicit instantiation: 只有用到时候才实例化: declare pointer(只是declare没有绑定) to class, 不会instantiate classs 比如Foo<int>*a class defintiion不会instantiated. 或者当有derived-to-base pointer conversion时候 or delete pointer
  • 如果class static member, 只有用static member时候才会实例化
  • 有时候compiler 不能deduced type(比如 return type or normal conversion) ,需要Function explicit arguments. explicit argument from left to right. long lng; compare(lng, 1024);, error 因为没有compare(long,int), compare<long>(lng, 1024); correct, 1024 convert to long
  • Trailing Return Types:deduce decltype return type from parameters. 因为trailing return appears after parameter list
  • header type_traits type tranformation for template parameter. 如果返回是type_traits::type 必须加typename, 告诉compiler we use type
    • 如果not possible to transform, type_traits::type is template parameter, 比如remove_pointer<T>::type, if T is int*, return type is int, 如果 T is int, return type is int
  • assign or intialize a function pointer, compiler use type of pointer(等号右边)值 to deduce template. int (*pf1)(const int&, const int&) = compare; compare is template
  • template <typename T> void f(T &p); 只能pass lvalue, 如果T 含有const, 只能是lower-level, not top level. , 比如const char *
  • template <typename T> void f2(const T&p); parameter 的 const 可是top/lower level, 比如const char * const &p T是 char to const object (T中的const 是 lower-level), const T&pconst 是 top level 指pointer is const pointer. 比如const int&, T是int, const 是lower level的
  • rvalue reference collasping
    • 可以pass 任何type to template function with rvalue reference
    • pass rvalue to T&&, T is rvalue type (not reference)
    • pass lvalue to template function with rvalue reference 是, T是 lvalue reference
    • reference collasping only applies 当 reference to reference is created indirectly 比如type alias or template parameter
    • 比如function paramter is T&& val, function内 T t = val; 如果pass rvalue is copy value of val, 改变t, 不改变外面值 . 如果pass is lvalue(T is reference type), is binds reference to val, 改变t, 改变外面值
  • move: 返回的type是remove_reference<T>::type&&always return rvalue reference. 对于rvalue, cast does nothing, 对于lvalue, static_cast<remove_reference<T>::type&&> (t) t 是lvalue reference
  • forward<T>::type
    • 必须called with explicit argument type
    • return rvalue reference (T&&) to that explicit argument type
      • if argument is rvalue, type is rvalue oridinary type. return rvalue reference
      • if argument is lvalue, type is lvalue reference, return Type& && is lvalue reference
    • preserve all the type information by using rvalue reference and std::forward<T>(t)
    • 如果不用rvalue reference, 不work 的例子
    • 如果不用forward, 即使rvalue reference 也会被当做lvalue to pass 给 template function 内的另一个function
  • template matching rule:
    • 根据conversions 来排序 (array/function to pointer, lower const conversion), 如果several functions viable
      • 如果有nontemplate function, 选nontemplate function
      • 其次选择more specialized 比如 T* p more specialized than const T& p
    • 看overloading match 例子
    • 注意string literal types"Hi" is const char array, 所以有const T&aconst T*a 都available 优先call pointer的, 因为 array to pointer conversion 属于exact match, const T*aconst T&a 更specialized
    • rvalue reference 只用于forwarding argument or template overloading
      • template <typename T> void f(T&&); binds to nonconst rvalues and lvalues
      • template <typename T> void f(const T&); const lvalues and rvalues, 因为有const conversion (不是top的),所以上面的不是exact match
      • 如果同时T&&const T& 对于nonst lvalue, call T&& 因为更specialized 的
  • Variadic Templates: varying parameter is parameter pack, 大于等于0个不同类型 parameters
    • sizeof...(pack) 有多少个pack
    • initializer_list used varying number of parameter with the same type, Variadic functions used when different type
    • vardiadic function 通常是recursive的, process first one and call iteself on remaining
      • 需要一个nonvariadic version stop recursion, 如果fail to declare before variadic, 无限的recursion
  • Expand Pack: put ellipsis to right of the pattern
  • Forward Pack: rvalue reference + forward: std::forward<Args>(args) ...: expands both template parameter pack Args and function parameter pack args.
  • specialization
    • function: 必须为其提供arguments for every template parameter.
    • function specialization is instantiation. 不是overload function, 不影响function match, 如果定义normal template 和 specialization, 选择specialization (其实只是一个实例化), (根据matching rule, more specialized)
    • 不可以partial specialized function template
    • class template 可以 partial specialized , 在class 名字后的 <> specify template parameter we are specializing, 与原模板中 的参数 按位置对应. 保留一部分template parameter, specialized 一部分 template parameter
    • 可以只specialize members 而不是whole class (比如hash<Sales_data>)
    • declaration for a specialization must be in scope before any code uses that instantiation, 否则compiler generate code using original template
  • hash<key_type>: 必须定义
    • 1 call operator returns size_t and parameters is key_type
    • 2 result_type (size_t) and argument_type (就是key_type 比如 Sales_data)
    • 3 default constructor and copy-assignment constructor (can be implicitly synthesized)
    • 必须specialized in namespace std as defined.
    • 如果使用 key_type(Sales_data) 的 private member, 需要make hash<key_type> as Sales_datafriends
    • key_type 需要有 == 定义
    • hash<Sales_data> definition starts with template<>, which indicates that we are defining a fully specialized template

17.Specialized Library Facilities

  • tuple<T1,T2,...,Tn>t are value initialized
  • tuple<T1,T2,...,Tn>t(t1,t2,...,tn): constructor is explicit (direct initialization 不能conversion constructed)
  • make_tuple<v1,v2,...,vn): type inferred from types of initializer
  • t1==t2: member 必须有一样的数量。 一旦发现不一样, 后面不用比较了
  • tuple Relational operations: tuples 必须有一样数量member
    • 因为tuple defines <and ==operators, 可以pass sequences of tuples to algorithms and use tuples as key type in ordered container
    • 如果同样位置type 不可比也是error, 比如 第2位置一个是string, 一个是int, 不可比
  • get<i>(t): return reference,如果t is lvalue, result is lvalue reference, otherwise it is rvalue reference.
    • i 必须是 integral constant expression
  • 所有member of tuple 是 public
  • tuple_size<tupleType>::value: size_t type. public constexpr static data member, 可以用于array declare, 比如 int a[tuple_size<..>:value]
    • 如果tupleType 不知道, 可以用decltype
  • tuple_element<i, tupleType>::type: type of specified members in specified tuple type

  • bitset possible to deal with collections of bits 大于 longest integral type
  • bitset has fixed size size must be constant expression
  • low-order bits: 在0的位置, high-order bits:在最后位置
  • 如果用的bitset 小于given number, 只有lower-order bits are used, higher order bits 大于size的 被丢弃
  • initialize bits from string char pointer, char pointer lower index 给bitset high order,听起来绕口, 跟读数字顺序是一样的
  • bitset<n>b(u) copy of n low-order bits. Constructor 是 constexpr and explicit
  • bitset<n>b(s, pos, m, zero, one) , bitset<n>b(cp, pos, m, zero, one) pos default 是 0,, m default 是 string::npos, zero default to ‘0’, one default to ‘1’
  • set(set at pos for bool value v), reset(turn off bits), flip, 是overloaded, 如果没有提供argument, apply to entire sequence
    • ~bitvec[0]; 等于 bitvec.flip(0)
  • b.size() is constexpr .
  • b.to_ulong() 如果等号左边的range 小于 它, throw overflow_error
  • bitset<16> bits; cin >> bits;: read up to 16 1 or 0 characters from cin, or encounter character 不是1 or 0, or it encounters end-of-file or an input error
  • bitset subscript overloaded on const; const version: 返回bool if given index is on. nonconst version 返回type on bit lets us manipulate the bit value at the given index position
  • regex_match需要entire sequence match
  • regex_search 只要有substring in the input sequence matches
  • regex_search and regex_match 都返回bool, argument (seq, m, r, mft), match object, match flag type optional, seq是 string, iterator, or null-terminated array,
  • regex operation:
    • regex r(re,f): re 可以是 string,iterators denoting a range of characters, pointer to a null-terminated character array, a character flag f optional
  • sregex_iterator call regex_search to iterate through matches,
    • constructor sregex_iterator it(b,e,r), b,e iterator(e.g.string), r object
    • 是iterator adaptors bound to an input sequence and regex object, 当绑定后, 自动定义位到first match in given string
      • constructor call regex_search
    • dereference iterator, get an smatch object corresponding to results from most recent search
    • 当increment iterator, calls regex_search to find next match in string, prefix returns next match, postfix returns old value.
    • iterater 比较, 如果都是off-the-end 则相等. 如果是绑定到same object and input sequence, 也相等
  • smatch operation: ready()(如果被set 到了regex_search or regex_match), prefix()(sequence before match),suffix(), size()(等于match 的 subexpression 个数加1)
    • smatch subexpression 操作(是在smatch object 上而不是 ssub_match object上): m.length(n), m.position(n)(distance of nth subexpression from start of sequence), m.str(n), m[r](获取ssub_match object)
  • smatch for string, cmatch for char array, wsmatch for wide string, wcmatch wide char array
  • first ssub_match from smtach index at 0 表示match entire pattern
  • subexpression use parentheses to denote
  • [-. ] match dash, dot 空格. note: dot in bracket no special meaning
  • Operation on ssub_match
    • matched: 表示whether ssub_match was matched, 比如optional ? match了
    • first second : match range
    • length(): Returns 0 if matched is false
    • str(): string containing the matched portion
    • s=ssub : 等于 s = ssub.str(), the convert operator is nonexplicit
  • to escape, must use 双backslash, \\
  • 可以把regular expression 想成 simple programming language, not interpreted by C++ compiler, compiled at run-time.
  • regular expression 语法正确与否 evaluated at run time. very slow, constructor regex outside the loop
  • reg_replace: r: regex object, fmt format, seq can be string or pointer to null-terminated array
    • regex_replace(dest, seq, r, fmt, mft): dest is output iterator
    • regex_replace(seq, r, fmt, mft) 返回string
    • $i 表示第i个subexpression
  • rand: uniformly distributed)的 伪随机数(pseudorandom integers), 0 到system-dependent maximum value(RAND_MAX): 问题是当需要floating-point number or non-uniform distribution. need to transform the range, type or distribution
    • rand()/RAND_MAX: precision 低于random floating-number. some floating-point values that will never be produced
  • An engine generates a sequence of unsigned random numbers.
    • function-object classes 定义了call operator takes no arguments and returns a random unsigned number
      • default_random_engine e1; e1()
    • ibrary defines several random-number engines that differ in terms of their performance and quality of randomness.
    • 大多数情况, the output of an engine is not directly used -e.min(), e.max() range 最大值最小值
    • e.discard(u): advance engine by u(unsigned long long) steps,
    • e.seed(s): reset state of engine using seed
      • 另一方法seed engine when create engine, e.g. default_random_engine e2(32767)
      • 通常用 time() to provide seed,, 因此这种方式值用于生成种子 间隔为秒级 或更长的应用
    • 即使是random, 每次return the same sequence of number, useful for debugging
      • make that generator (both the engine and distribution objects) static
  • A distribution uses an engine to generate random numbers of a specified type, in a given range, distributed according to a particular probability distribution.
    • engine pass to distribution, u(e) , 不可以是 u(e())(是value passed to distribution)
    • 默认template argument: 用<> after template name
      • double: distribution 用于生成floating-point
      • int: distribution 用于生成integral type
    • normal_distribution<> n(4,1.5); mean 4, standard deviation 1.5
  • bernoulli_distribution b is not template 是class. always return a bool value given probability
  • C++ programs 不应该使用 rand, 而使用 default_random_engine along with an appropriate distribution
  • declare engine and distribution (may retain the state) outside loop 否则每次数都一样

18.Tools for Large Programs

  • when throw exception, 在throw 之后语句不会被执行, control is transferred from throw to catch.
    • functions along the call chain 可能提早退出 (prematurely exited):
      • compiler 需要保证 objects properly destroyed. class 会自动call destructor. 而built-in type no work
    • when enter handler, objects created along the call chain from throw 将被destoryed
  • stack unwinding: continue search in chain of nested function calls until catch is found
    • 如果catch is found : program continue by executing code inside catch
    • 如果没有catch found, terminate
  • exception in constructor: 部分initialized and 需要保证constructed member properly destroyed.
  • exception in destructor: 如果没有被catch, terminate is called.
    • never throw in destructor 如果不能handle 的话
    • 因为 free resources, destructor unlikely throw exceptions
    • 所有standard library types 保证 destructor not raise exception
  • exception object: compiler use the thrown expression to copy initalize. 所以expression 需要有 complete type.
    • 如果expression 是 class type, destructor and copy/move constructor accessible
    • 如果expression 是 array or function type, expression converted to pointer
  • throw a pointer to local object
  • Expression 的static type 决定了 type of exception object. -比如throw 一个dereferences pointer是base type points to derived-class object, thrown 是被 sliced-down. 只有 base-class 被thrown
  • exception declaration: 只有一个parameter, name 可以忽略, type 可以是value / lvalue reference, 但不能是rvalue reference
    • parameter 是initialized by exception object
      • 如果parameter is nonreference, copy of exception object. parameter 的change是local的, 不会影响exception object
      • 如果parameter is reference, change to parameter also made to exception object
      • 如果parameter is base-class type, 可以initialized by an exception object derived from parameter type
        • 如果parameter is nonreference , object is sliced down
        • 如果parameter is reference, derived-to-base conversion
  • rules for exception matches exception declaration:
    • conversion from nonconst to const
    • derived-to-base conversion
    • Array/function convert to pointer
    • 不允许 arithmetic conversion and class conversion
  • catch 发现的不一定是最匹配的,而是first one match exception:
    • most specialized catch appear first
    • most derived type 放前面, least derived type 放后面
  • catch-all:
    • 必须最后出现
    • 通常combine with rethrow
  • rethrow: pass exception further up the call chain to another catch
    • empty throw 只能出现在 catch or in a function called from a catch
    • rethrow 不specify an expression
    • 如果改变parameter 再rethrow, 只有parameter 是reference时候才会pass up the call chain to another catch
  • function try block: only way handle constructor initializer list
    • 普通try block in constructor can’t work,
    • exception when initialize parameter 不能被function try block handle, 只能由caller handle
  • noexcept specifier: 要么声明在所有的declaration 和 definition 要么就不声明, 在const reference qualifer 之后, final, override, virtual function =0, constructor initializer list 之前
    • 不属于function type一部分
    • 可以用于function pointer
    • 不能用于 typedef 和 type alias
    • 如果function 声明了 noexcept 却throw, no check at compile time and call terminate at run-time
    • optional argument takes bool: 表示可不可以throw exception
  • noexcept opertor: unary operator, returns a bool rvalue constant expression 表示 expression 会不会throw, 像sizeof, noexcept 不会 evaluate its operand.
  • function pointer和 pointed function 必须有相同 compitable specifications
    • 如果function pointer 有 noexcept(true), function 必须是 noexcept(true)(nonthrowing exception specification)
    • 如果function pointer 没有 noexcept(true), function 有没有```noexcept(true)`` 无所谓
  • 如果virtual base promise not to throw, inherited virtual 都必须是not to throw. 如果base 没有not to throw, virtual throw 不throw 都可以
  • 如果all members and base classes promise not to throw, 则synthesized 是not to throw. 如果任何function invoked by synthesized member can throw, 则 synthesize member is noexcept(false)
  • 如果 没有 provide exception specification for destructor, compiler 会synthesizes one with exception speciaition 与compiler 假设synthesized 的destructor类型一样
  • exception class 只定义 copy constructor, copy-assignment operator, a virtual destructor, and a virtual member named what: what 返回 const char*(null-terminated char array): guarantee not to throw
  • runtime_error: 表示can be detected only when program is executing
  • logic_error:表示我们可以从程序代码中发现的错误
  • exception, bad_cast, and bad_alloc define default constructor
  • runtime_error and logic_error classes no default constructor but have constructors that take a C-style character string or string argument

  • Namespace Name must unique
  • cannot define namespace inside function or class
  • A namespace scope does not end with a semicolon.
  • Each namespace is a scope. Different namespaces introduce different scopes
  • not put #include inside the namespace, 比如 std in cplusplus 表示 std in out defined scope.
  • Nested Namespace: hide the declaration of outer namespace
  • inline namespace: can be used as direct members of enclosing namspace 不需要提供inline namespace name. 只需提供enclosing namespace name
    • keyword inline must appear on first definition of the namespace
    • 用于多个版本,现在版本inline, old 版本noninline
  • Unnamed Namespace:
    • variable in unnamed namespace has static lifetime, created before first use and destroyed when program ends
    • discontiguous within given file 但是不能span files,两个files 的unnamed space not related
    • 如果include header 有 unnamed namespace, 在不同include 这个header 文件中定义属于每个file的entity
    • 可以nested in another namespace, access using enclosing namespace name
  • inline namespace and Unnamed Namespace: 他们都是in the same scope as the scope at which unnamed namespace/inline space is defined.
    • 区别是inline namespace 自动是same scope, inline Namespace 是在 using directive 时候在same scope
  • namespace Alias: using aa = int;
    • 一个namespace 可以有多个aliases. can be used interchangeably.
  • using declaration: introduce one member at a time.
    • 可以是global, local, namespace, class scope,
    • 在class scope 中只能用于refer base class member
  • using directive:
    • 可以用于global, local, namespace, 但不能用于class scope
    • lift namespace members 到 nearest scope that contains both namespace itself and using directive
      • local definition 可能会hide namespace definition.
    • 跟 Using Declaration 相同之处是: use unqualified form of a namespace name
    • 跟 Using Declaration 不同是,
      • namespace 中所有 名字都是visible without qualification
      • clash (命名冲突) is detected in using declaration, but for using directive, 只有被用到时候, 才会发现
    • 如果用很多using directive,可能有name collision problems
  • 避免header 有using directive or using declaration 否则将名字inject 到用到这些header 文件
  • Argument-Dependent Lookup: operator>>(std::cin, s); 不同specify using std::string
    • search namespace where argument class is defined in addition to normal lookup
    • 因为move and forward parameter is rvalue reference (can match any type), 因此用 std::move specify the version, avoid collision
    • 当make a function as friend, no visible, 但是friend function take class as parameter, visible, 因为Argument-Dependent Lookup
    • 根据Argument-Dependent Lookup, 确定candidate set, 即使不在same namespace
  • overloading:
    • using declaration:
      • all the versions brought into current scope
      • 如果name and parameter list 都一样, using declaration is error
    • using directive:
      • 如果name and parameter list 都一样, not an error 除非用的时候是error
  • 没有限制number of base class in derivation list, 但一个base class 只能出现一次
  • derived constructor initialize all base classes (subobject)
  • base class 顺序只与order of derivation list 有关, 与constructor initializer list 无关
  • destrutor always invoked in reversed order as constructor
  • derived 的 synthesize copy/move constructor / assignment 自动call base class 的(self-defined or synthesized)。 derived 的 self-defined 的 copy/move constructor / assignment 不会自动call
  • static type 决定可以用 那个member
  • Multipe Inheritance: lookup happens simultaneously among all the direct base classes.
    • 如果 name is found more than one base class, Unqualified uses of that name are ambiguous
    • Name Lookup before type checking: 即使不同 base classes 中funtion name 一样 但是有 different parameter lists, derived class call will be ambiguious
      • 即使一个是private, 一个是public, 也是error
    • inherit constructor using Base1::Base1; from multiple base class, 如果有一样的parameter list error
    • Derived-to-base conversion: converting to each base class is equally good..
      • 比如 most derived -> derived 和 most derived -> base conversion 在compiler 看来一样好
  • Virtual Inheritance: willing to share its base class
    • Virtual derivation affects the classes that subsequently derive from a class with a virtual base; 并不会影响 derived class 本身.
    • public virtual or virtual public 顺序都可以
    • 可以用derived-to-base conversion, 可以用virtual base pointer/reference 绑定到 derived class
    • visibility: access by derived class
      • virtual base √
      • 如果 virtual base 只被一个derivation path override √
      • 如果 virtual base 只被多个derivation path override ×
    • virtual base is initialized by most derived constructor
    • virtual base 最先初始化,多个virtual base 初始化顺序根据derivation list 由左到右顺序决定, 之后initialize non virtual base class, 顺序也是由derivation list 由左到右顺序
    • destructor 与 constructor 顺序相反

19.Specialized Tools and Techniques

  • dynamic_cast pointer/reference base type into derived type
    • dynamic_cast<type*>(e), dynamic_cast<type&>(e)
      • e 必须是 public derived from type, public base of type, or type itself
    • dynmaic_cast to pointer type fails, result is 0. to reference type fails, throws bad_cast
    • pointer-type dynamic_cast:
      • 至少一个 virtual function and Derived public derived Base
  • typeid operator, returns the type of given expression, typeid(e)
    • e is any expression or a type name
    • 返回是 reference to const object of type_info or type publicly derived from type_info
    • top-level const 被忽略
    • apply to array or function, not convert to pointer
    • 当apply not class or class without virtual functions, 返回是 static type
    • 当operand 是 class type has 至少一个 virtual function, evaluate at runtime static type 可能不同于 dynamic type
    • typeid(*p) 返回是 static, compile-type of pointer
    • typeid(*p) 如果 p point to a type 没有 virtual functions, p 无须是valid pointer, 如果 p 是null pointer, throws bad_typeid exception
    • 注意比较pointer 类型需要加 deference pointer, 否则比较是 pointer type to class type , never equal
      • 比如 typeid(bp) == typeid(Derived) always fail, 正确是 typeid(*bp) == typeid(Derived)
  • type_info
    • provide a public virtual destructor
    • 当compiler provide 额外type information, normally does in a class derived from type_info
    • no type_info default constructor, copy/move constructor / assignment operators are defined as deleted
      • 唯一constructed 方法是 through typeid operator
    • typeid(id).name() 返回 C-style character string that is a printable version of the type name.
      • Guarantee: it returns a unique string for each type. not required to match the type names as used in program
  • Enumeration:
    • group together sets of integral constants
    • Enumerator are const, initializer 必须是 constant expression
      • 比如定义 constexpr, switch, 可以 use an enumeration type as anontype template parameter, 可以initialize class static data members of enumeration type inside class definition
    • like class, each enumeration defines a new type
    • are literal type
    • Scope Enumeration: enum class
      • 可以像class 一样定义object
      • Names of enumerators inaccessiable outside enumeration, Access 需要 enumeration name + scope operator
      • Enumerator 不会自动 convert to integral type
    • Unscope Enumeration
      • 只能define objects only as part of the enum definition.
      • Names of enumerators in the same scope as enumeration
      • unscope enumeration type object 自动 conver to integral type
        • 可以pass unscope enum to function with integral type parameter
    • Enumerator value 不需要 unique, 如果忽略initializer, enumerator 默认值是比前一个大1
    • Enumerator 只能被 enumerators or another enum type assign / initialize, 不能被integral type 初始化
    • underlying type: enum intValues : int ...
      • scope : int, unscope: no default but enough big to hold enumerator values
      • error: 如果 enumerator > specified underlying type
    • forward declarations: 必须specify underlying type (explicitly or implicitly(scoped))
      • unscope no default, 必须specify type for unscope
      • size of one enum 必须一样 for all declarations and definition
      • 不可以declare a name as unscope enum in one context and redeclare it as scoped enum later
  • Pointer to class:
    • can point to a nonstatic member
    • static class member 不是object 一部分. Pointer to static members are ordinary pointer
    • pointer to member data
      • 当initialize or assign pointer to member, no need to provide specific object.
      • when dereference a pointer to member 再supply object.
      • format: string Screen::*pdata;: * declaring a pointer to member
      • const string Screen::*pdata; underlying data is const, pointer is not const
      • if underlying data is private, must use pointer-to-member inside class, or error
    • pointer to member function
      • const member or reference member, must include const or reference in pointer declaration
      • must use & to initialize or assign, no conversion from member function to pointer to member
      • if function overload, must declare explicitly, cannot use auto.
      • pointer to function member can be used as return type or parameter (可以有default parameter)
      • (pScreen->*pmf)();: 括号不可少, bc precedence
    • callable object
      • bc call a pointer to member, must use .* or ->* operators to bind pointer to specific object. a pointer to member is not a callable object, cannot pass to algorithm
      • Method 1: function tempolate: first parameter represent the object which member will run
        • must specified either pass as pointer or reference
      • Method 2: mem_fn:
        • 与 function 一样: mem_fn: generate a callable object from a pointer to member
        • 与 function 不同: mem_fn deduce the type of callable from the type of the pointer to member
        • can be called either on object or pointer
      • Method 3: bind
        • Like mem_fn, the first argument can be a pointer or a reference
  • Nested Class
    • no connection between enclosing class and nested class
    • if definition of nested class outside before seen, it is imcomplete type
    • static member definition outside enclosing class
    • namelookup: 只能access type names, static members, and enumerators from enclosing class without specifying enclosing class.
    • if define member outside enclosing scope, need to provde enclosing and nested class name
    • nested class cannot access enclosing name, but can access global name
  • Union: offer mutually exclusive values of different types
    • multiple data members, but only one member have a value. when one member is assigned, all other undefined
      • must know what type of value currently stored in union, retrive or assign value from/to wrong memeber, crash
    • storage of union is at least largest data member
    • A union cannot have reference member
    • class have constructor or desturctor can be union member
    • A union members can be public, private, protected. Like struct, default public
    • union cannot inherit from another class, or as base class, no virtual
    • By default union is uninitilized. when initializer present, used to initialize first member.
    • Anonymous unions: compiler create an unnamed object of newly defined union type
      • member cannot be private/protected member, 不能define member functions
      • member can be accessed directly in anonymous union defined scope
    • for built-in type member:
      • compiler synthesize memberwise default constructor / copy-control member
    • for union that have nontrivial class:
      • switch to class need to run constructor, switch from class, run destructor
      • member class that defines it default constructor or at least one copy-control member, compiler synthesized copy member as deleted
  • local class: A class defined inside function body.
    • only visible in scope in which it is defined
    • all members must be defined inside class body
      • if nested class defined inside local class, nested class must in the same scope as local class defined.
    • cannot have static data members\
    • namelookup: only type names, static variables, and numerators defined within enclosing scope
    • usually make it function as public, bc local class is encapsulation, no furthur encapsulation.
  • Bit-fields: a bit-field holds a specified number of bits
    • often used when program need to pass binary data to another program or hardware device
    • the memory layout of a bit-field is machine dependent
    • A bit-field must have integral or enumeration type
      • 通常用 unsigned type to hold a bit-field, 因为behavior of signed bit-field is implementation defined.
    • 定义方法是 : member_name : val: val 是 constant expression, number of bits
    • multiple bit-fields defined in consecutive order packed within adjacent bits of single integer
    • cannot apply & address-of, bc no pointers refer to bit-fields
    • usually use inline function to test / set value of bit-field
  • volatile: inherently machine dependent
    • value controlled by processes outside direct control of the program
      • 比如 value updated by system clock
    • tell compiler not perform optimizations on such object
    • a object can be defined both const and volatile
    • only volatile member functions can be called on volatile objects
      • can overload function based on volatile
    • only assign address of a volatile object to a pointer to volatile
    • difference between const and volatile:
      • synthesized copy/move operations cannot used to initialized or assign from volatile object, 因为synthesized member parameter is nonvolatile, cannot make nonvolatile <– volatile, Error
      • if want to make volatile objects copied, moved, or assigned, must define own copy/move operations. parameter 是 const volatile reerences,
  • Linkage Directives: extern “C”:
    • mix C++ with code like C need compiler access C and C++ compatible
    • linkage directive cannot be in class or function definition. The same linkage directive must appear on every function declartion
    • inkage directive 形式是 extern + string literal\
    • header: 当一个 #include directive enclosed in compound-linkage directive, header 中所有functions 被认为是由 linkage directive 语言编写的。
      • linkage directive can be nested, if function has own linkage directive in that header, function linkage directive unaffected
    • pointer to function must be declared with the same linkage directive as function
    • error to assign two pointers with different linkage directive
    • when use linkage directive, apply to both return / parameter type
      • when want to pass a pointer of C function to C++ function, must use type alias
    • 值得注意是: 可被共享的函数 reurn type and parameter type are often constrained. 比如, 我们不能pass objects a nontrivial C++ class to C program.
    • _ _cplusplus: 即使是 extern "C" is compiled under C++
    • interaction between linkage directives and function overloading 取决于 target language.
      • 比如 C 不支持 function overloading.
      • 可以是定义一组function, extern "C" 只被 C called, 其他的被C++ called

2. Variable and Basic Types

Initialization is not assignment. Initialization happens when a variable is given a value when it is created.(创建变量时 赋予它一个初始值). Assignment obliterates an object’s current value and replaces that value with a new one ( 把对象当前值擦除,用一个新值代替)

(a). Initialization, Definition, Declaration, Scope

(1). List Initialization

//下面四种初始化都正确

int units_sold = 0;
int units_sold = {0};
int units_sold {0};
int units_sold(0);

List Initialization will throw error if the initializer might lead to the loss of information. (编译器将会报错,如果使用列表初始化且初始值存在丢失信息的风险). 如果不用List Initialization 就不会, 会implicit convert

long double id = 3.1415026536;
int a{ld}, b = {ld}; //error: 存在丢失信息风险

int c(ld), d = ld; // 转化执行,但是丢失了部分值 (truncated)

(2). Default Initialization

Most classess let us define objects without explict initializers. Such classes supply an appropriate default value. e.g. std:string s; s implicitly initialized to the empty string.

(3). Variable Declarations and Definitions

  • A declaration makes a name known to the program. A definition creates the associated entity.
  • A variable declaration specifies the type and name of a variable. A variable definition is a declaration. In addition to specifying the name and type, a definition also allocates storage and may provide the variable with an initial value. 变量声明规定了变量的类型和名字,在这一点上定义与之相同. 但除此之外, 定义还申请存储空间, 也可能会为变量赋一个初始值
  • Variable must be defined exactly once but can be declared many times 变量只能被定义一次,但可以被声明多次
  • 如果想声明一个变量而非定义它,在变量前添加extern
extern int i; //声明i而非定义i

int j; //声明并定义j

任何包含了显示初始化的声明既是定义. 给extern 表含的变量一个初始值,抵消了extern的作用

extern int i = 5; //定义

(4). Nested Scopes

用 :: 访问global variable (explicitly request global variable)

int reused = 42;
int main(){
    cout << resused <<endl; // 42

    int reused = 0; //新建局部变量,覆盖全局变量

    cout << resused <<" global " << ::resused <<endl; // 0 , 42

}

(b). Compound Types

A compount type is a type that is defined in terms of another type, which is references and pointers

  1. reference, pointer:
    • 等号两边类型(type)必须相同 , 除了一个特殊情况 const reference
      • 比如 double dval = 3.14;,int &refVal5 = dval;//error
  2. 对于const非reference 非pointer, 等号左右两边等号类型可以不同.
  3. 对于const reference 等号两边可以不同类型,compiler会做转换 const double pi = 3.14; const int & i = pi;

Reference At here, reference is only lvalue reference not rvalue reference

  • reference must be assigned at initlization
  • non-const reference 不能assign rvalue, must be lvalue .
  • 程序把引用和初始值绑定(bind)到一起,而不是将初始值拷给引用, 引用无法绑定到另一个对象.
  • A reference is not an object. Instead, a reference is just another name for an already existing object
int val = 1024;
int &refval = val, &r = i; // correct

refval = 2; //val = 2

int &refval2; //error

int &refval3 = 10 ;//error: initializer must be an object

double dval = 3.14;
int &refVal5 = dval; // error: initializer must be an int object

We can define multiple references in a single definition

int i2 = 2048; // i and i2 are both ints 

int i3= 1024, &ri = i3; // i3 is an int;ri is a reference bound to i3 

int &r3 = i3, &r4 = i2; // both r3 and r4 are references

Pointer

  • assign pointer 需要assign 的类型和被assign 类型compatible.
  • Pointer is a object whereas reference is not object
  • It is error to copy or try to access the value of an invalid pointer. As when we use an uninitialized variable, this error is one that the compiler is unlikely to detect(编译器不会检查access invalid pointer). The result of accessing an invalid pointer is undefined.
  • Given two valid pointers of the same type, we can compare them use the equality(==) or inequality(!=) operator, Two pointers are equal if they hold the same address (两个指针存放的地址相同)
  • 修饰符 (type modifier *)仅仅是修饰一个variable, 而非修饰整行所有变量. Each declarator can relate its variable to the base type differently from the other declarators in the same definition.int* p1, p2, p1 是 int pointer, p2 是int, , 如果两个都是pointer 用int* p1, *p2
  • deference * 返回类型是reference,所以可以用int *p = &i; *p = 5;//change object
double dval;
double *pd = &dval; //ok: initializer is the address of a doub;e

double *p2 = pd;//ok: initializer is a pointer to double

int *pi = pd;//error: types of pi and pd differ, 一个int,一个double

pi = &dval; //error: assigning the address of a double to a pointer to int

//上面两个error 都会compile fail


int Ival;
double *dpi = &Ival;//error: types of pi and pd differ, 一个int,一个double, 也是compile error


& 赋值是改变等号左侧 pointer指代对象, * 不改变指代对象,改变的是指代对象的值

int r = 1;
int *p = &r;
int *p2 = p;
    
*p2 = 10; //change pointed value, aslo change value of the object pointer points to

cout << *p <<" , "<<*p2  <<" , "<< r <<endl; //print 10 ,10, 10

int c = 5;
p = &c; //change object that pointer points to

cout << *p  <<" , "<< r <<" , "<< c<<endl;//print 5, 10 , 5

int i = 1024, *p = &i, &r = i;// i is an int; p is a pointer to int; r is a reference to int

int* p1, p2;//p1 is a pointer to int; p2 is an int (not pointer), 

//上面的*, 仅仅修饰了p1, 而非让所有变量的类型一样都是int pointer

int *p1, *p2; // both p1 and p2 are pointers to int

void pointer: A void* pointer holds an address, but the type of the object at that address is unknown: . Generally,we use a void* pointer to deal with memory as memory, rather than using the pointer to access the object stored in that memory.

Pointers to Pointers

int ival = 1024;
int *pi = &ival; // pi points to an int

int **ppi = &pi; // ppi points to a pointer to an int


cout << "The value of ival\n"
    << "direct value: " << ival << "\n" 
    << "indirect value: " << *pi << "\n"
     << "doubly indirect value: " << **ppi << endl;//print 1024, 1024, 1024

References to Pointers: A reference is not an object. Hence, we may not have a pointer to a reference. However, because a pointer is an object, we can define a reference to a pointer:

int i = 42; 
int *p; //p is a pointer to int

int *&r = p;  // r is a reference to the pointer p

//cannot write as  int &*r = p; 

//because reference(not object) don't have pointer, but pointer has reference


r = &i;// r refers to a pointer; assigning &i to r makes p point to i

*r = 0; 
// dereferencing r yields i, the object to which p points; changes i to 0

离变量名最近的符号(symbol, 上面例子 &r 的符号& ) 对变量类型有最直接的影响, 因此r是一个引用;

(c). Const Qualifier

  1. 非reference, 非 pointer,Const Initialize时候 等号两边type可以不一样, e.g.
    • int a = 0; const double b = a;
    • double a = 0; const int b = a;
  2. reference or pointer, Const Initialize时候 等号两边type 必须strictly 一样

(1). Reference

  1. constvalue 必须initialize when define
  2. Const Value-Value:
    • non const -> const value e.g.int val = 5; const int cst = val;, 因为const value是通过复制生成,改变原来值,不会改变const value值 e.g. int val = 3; const int cstRef = val;val = 10;, cstRef = 3 , val = 10
    • const -> non const const (reference or without reference) initilize non const value; const int & cstRef = 3; int val = cstRef.
  3. Const Ref-Ref:
    • Const Ref -> Ref Error const reference 不能bind 到non-const reference,因为reference is not object const int & ref = 3; int &i = ref; //error,
    • Ref -> Const Ref ✔️ int val = 3; int & ref = val; const int &cstRef = ref;
  4. Ref-Value:
    • Ref -> Const Value ✔️, int val = 3; int & ref = val; const int cstRef = ref;
    • Const Value -> Ref Error const int cstRef = 3; int & ref = cstRef;, 因为Referene 不是object, 这么做有更改Const 可能性
    • Const Ref -> Value ✔️, const int & cstRef = 3; int val = cstRef;
    • Value-> Const Ref ✔️, int val = 3; const int & cstRef = val;
    • Const Ref -> Const Value ✔️,const int & cstRef = 5;const int val = cstRef;
    • Const Value -> Const Ref ✔️,const int val = 5;const int & cstRef = val;
  5. const reference, 如果type 不匹配 or 用rvalue expression, 会生成一个temporary object, 这样如果改了原来的值, const reference不会改 (因为const reference连得是temporary object) e.g. double val = 3.14; const int & cstRef = val;
    • 如果不是const reference, double val = 3.14; int & cstRef = val; 因为没有中间值生成, 改变val的值,有改变const reference 风险, Error
  6. const reference生成后 不能直接更改,但可能会被间接更改(没有temporary 中间值 生成) int val = 3;const int & cstRef = val;val = 10; , val = cstRef = 10

const value 必须initialize when define

const int i = get_size(); // ok: initialized at run time 

const int j = 42; // ok: initialized at compile time 

const int k; // error: k is uninitialized const

const value 可以生成non const value (反之也可以), 但是const reference 不可以生成 nonconst reference (nonconst reference 可以生成const reference)

int val = 42;
const int cst = val; // ok: nonconst val -> const val

const int &  cstRef = val;
int jVal= cst; // ok:const val -> nonconst val

int & jRef = cstRef; //error const Ref -> non const Ref

int &jVal2 = val;
const int & cstRef2 = jVal2; //ok: non const Ref -> const Ref

const int bufSize = 512; the compiler will usually replace uses of the variable with its corresponding value during compilation. That is, the compiler will generate code using the value 512 in the places that our code uses bufSize. Compiler会找到代码中所有用到bufSize的地方,然后用512代替。

//不能用非const reference 指向const object

const int ci = 1024;
int &r2 = ci;// error: non const reference to a const object

int i = 42;

One exception for const references: initailize const from any expression that can be converted to the type of the reference. 当initilize const reference, 我们可以用任意表达式.

int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object 

const int &r2 = 42; // ok: r1 is a reference to const

const int &r3 = r1 * 2; // ok: r3 is a reference to const

int &r4 = r1 * 2; // error: r4 is a plain, non const reference

to understand this exception in initialization rules:

double dval = 3.14;
const int &ri = dval; //okay,

dval = 6.28;
cout<<ri << ","<<dval<<endl; //print 3, 6.14


int Ival = 5;
const int & ri2 = Ival; //okay,
const int & ri3 = Ival*2; //okay,
    
Ival = 30;
cout<<ri2<<" ,"<<ri3 << ","<<Ival<<endl; //print 30, 10, 30

上面例子中.To sure object to which ri is bound is an int, compiler transforms the code into something like (但不适用ri2, 因为initialize ri2时, 没有生成temporary object). In this case, ri is bound to a temporary object. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression.

const int temp = dval;//create a temporary const int from the double

const int &ri =temp;//bind ri to that temporary

const int temp2 = Ival*2;//create a temporary const int from the int

const int &ri3 =temp2;//bind ri to that temporary

A Reference to const May Refer to an Object That Is Not const: 当const referenceinitialized时, 并没有generate temporary object, 可能会被间接更改const refernce的value

int i = 60;
int&r1=i; //r1 boundto i

const int &r2 = i; //r2 also bound to i; but cannot be used to change i


r1=0; // r1 is not const; i is now 0
//r2 = 0;//error: r2 is a reference to const

cout<<i<< ", "<<r1<<", "<<r2<<endl; //print 0, 0 , 0

(2). Pointer and const

  1. Level Const
    • low-level const(object is const): const 在pointer/reference 左面表data is const, cannot change underlying data value. but can point to different underlying. const int & j = 5是 low -level const
    • top-level const(pointer is const): const 在pointer 右面有pointer在时候表示 pointer is const (a const pointer), 如果没有pointer 在表示数据const (const int a = 0, 不能改变指向对象,但可以改变the value of underlying object)
  2. The distinction between top-level and low-level matters when we copy an object. When we copy an object, top-level consts can be ignored: e.g. const * const int = const * int
    • 对于pointer之间的copy, 如果忽略Low Level const, Error
      • Pointer of Const -> Pointer: Error e.g. int val = 42; const int * p3Low = val;int * p3 = p3Low;//error
      • Const Value -> Pointer Error e.g. const double cstVal = 3.14; double *ptr = &cstVal; //error
      • Const Pointer -> Pointer:✔️ 因为可以忽略top-level constant e.g. int i = 0; int * const p = &i; int *a = p;
    • 对于value 到 const pointer 的copy, 可以忽略low-level const
      • Value -> Const Pointer ✔️ e.g. int val = 1; int const * const pLowTop = & val;
      • Const Pointer -> Value ✔️ int j = 0; const int * cstPtr = & j; int val = *cstPtr;
      • Pointer -> Const Pointer ✔️ int i = 0, *a = & i; const int * const p = a;
  3. The types of a pointer and the object to which it points must match(等号左右两边类型必须match).
int i = 0;
int *const p1Top = &i; // const is top-level; we can't change the value of p1Top

const int ci = 42; //  const is top-level; we cannot change ci

const int *p2Low = &ci; //  const is low-level; we can change p2Low

const int *const p3LowTop = p2; // right-most const is top-level, left-most is not

const int &r = ci; // const in reference types is always low-level


//top level constant can be ignored

i = ci; // ok: copying the value of ci; top-level const in ci is ignored 

p2Low = p3LowTop; // ok: pointed-to type matches; top-level const in p3LowTop is ignored


On the other hand, low-level const is never ignored.(lower-level 从不会被忽略). When we copy an object, both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects(拷贝 输出对象必须具有相同的底层const 资格). In general, we can convert a nonconst to const but not the other way round:

int *p = p3LowTop; // error: p3LowTop has a low-level const but p doesn't

p2Low = p3LowTop; // ok: p2Low has the same low-level const qualification as p3LowTop

p2Low = &i; // ok: we can convert int* to const int*

int &r = ci; // error: can't bind an ordinary int& to a const int object 

const int &r2 = i; // ok: can bind const int& to plain int

The first exception is that we can use a pointer to const to point to a nonconst object: 可以用const pointer指向non-const object

const double cstVal = 3.14; // pi is const; its value may not be changed

double *ptr = &cstVal; // error: ptr is a plain pointer

const double *cptrLow = &cstVal; // ok: cptr may point to a double that is 

*cptrLow = 42; // error: cannot assign value to *cptr

double dval = 3.14; // dval is a double; its value can be changed
 
cptrLow = &dval; // ok: but can't change dval through cptr

double pi = 3.14159;
const double *const pip = &pi; 
// pip is a const pointer (在pointer右边的const) to a const object

(d). Constexpr

A constant expression: value cannot change and can be evaluated at compile time. A literal is a constant expression. By declaring constexpr, we can ask compiler to verify that a variable is a constant expression.

Const 的缺点, 有时候我们需要constant expression, 但用const, 生成却不是constant expression. 比如, 下面例子even thoughsz is a const, the value of its initializer is not known until run time

const int limit = 20;
//limit is a constant expression

const int sz = get_size(); 
//sz is not a constant expression

constexpr int sz2 = get_size(); 
//只有当get_size() is a constexpr function, sz2才是constexpr function

The type we can use in a constexpr declaration are known as literal types.

  • arithmetic, reference, pointer types are literal types,
  • string types 不是literal types, 不能用于define varaibles as constexpr. varaibles defined inside a function 因为没有fixed address, 不能用constexpr,
  • The address of an object defined outside of any function is constant expression, and some objects have fixed address so may used to initialzie a constexpr pointer.

  • Pointers and constexpr
const int * p = nullptr; 
//p is a pointer to a const int (low level)

constexpr int * q = nullptr
//q is a const pointer to int (top level)

p and q are quite different. The difference is constexpr imposes a top-level const on pointer

constexpr int * pTop = nullptr; 
//pTop is a constant pointer to int that is null

const int tempInt = 10;
pTop = & tempInt; //Error, cannot assign const int to int * const


int j = 0;
constexpr int i = 42; // i is const int

constexpr const int * pLowTop = &i; 
// pLowTop is const pointer to the const int i

constexpr int * p1Top = & j
//p1Top is a const pointer to the int j

constexpr int * pTop2 = &i; 
// error, fix it is const int * pLow = & i

(e). Type Aliases

Type Alias is a name that is a synonym for another type

  1. typedef: typedef double wages, typedef can also include type modifiers (such as reference, pointer) that define compound types
  2. using: using SI = Sales_item;

Pointers, Const, and Type Aliases

typedef char *pstring;
const pstring cstr = 0; 
//cstr is a constant pointer to char

//是pointer const, 而不是数据类型data type是const 

const pstring* ps; //ps is a pointer to a constant pointer to char

const pstring(不等于 const char *) is constant pointer to char, not a pointer to const char, 如果直接replace the alias with its correspoinding type 是错误的, Error, 如果想要pointer to const char, 需要重新定义typedef const char * pstring

(f). Auto

  • auto tells the compiler to deduce the type from the initializer. By implication, a variable that uses auto as its type specifier must have an initializer.
  • auto can define multiple variables in single line, 但是initializers for all the variables in the declaration must have types that are consistent
  • auto is not always exactly the same as initializer types. 当用auto时候,top level const通常会被忽略, low-level const 会被kept,
  • auto & or auto* to auto-deduced type, 会keep top level, low-level const
  • 不能用auto to deduce the type from a list of initializer auto a = {1,2,3}; //error
  • Deduce:
    • auto deduced int& or const int& is int
    • deduce auto v = &i from reference is pointer
    • deduce auto v = *i from deference is value (因为deference 返回是reference,但是auto对于reference deduce是value)
    • 用reference 表示deduce 是reference type, 用pointer 表示deduce是pointer type, 会keep top-level const, auto & m = cval, *p = & cval; m是reference type, p是pointer type
    • auto deduced array 是pointer type

可以定义多个variables in single line, 但是需要都是一个类型(type)的

auto i = 0, *p = &i;
//okay i is int and p is pointer to int

auto sz = 0, pi = 3.14; 
//error: inconsistent type for sz and pi

忽略top-level const and keep lower-level const

int i = 0, &ref = i;
auto a = ref; // a is an int


const int cval = i, &cstRef = cval;
auto b = cval; //b is an int (top-level const dropped)

auto c = cstRef; //c is an int (cr is an alias for cval whose top-level const dropped)

auto d = & i; //d is an int*

auto e = &cval; 
//e is const int * (& of a const is low-level const, 把top-level 转成low-level)

auto + reference

auto & g = cval; // g is a const int & 

auto & h = 42; //error, can't bind a plain reference to a literal

const auto & j = 42; //const reference

Deduced 的type 必须consistent

auto k = cval, &l = i; // k is int, l is int& 

auto & m = cval, *p = & cval;
//m is const int&, *p 是  const int * (a pointer to const int)

auto &n = i, *p2 = & cval;
//error, n is int&, p2 is const int * (not int*)

(g). Decltype

  • decltype: compiler analyzes the expression to determine its type but does not evaulate the expression
  • decltype array 是array type, 而不是pointer type
  • handles top-level const subtly different from auto
    • variables: include top-level const and reference
    • expression: we can get the type that expression yields. 注:Dereference operator(*) yields type as reference
    • function, decltype(functionName) 返回的是function return type, not a pointer to function type.
    • decltype and auto difference: deduction done by decltype depends on the form of its expression.
      • 切记decltype((variable))结果永远是引用,decltype(variable) 只有当variable 本身是引用时, 结果才是引用

compiler 不会called f, 但是uses the type that such a call would return as type of sum.

decltype(f()) sum = x; //sum has whatever type f returns 

use decltype for variable, returns type including top-level const

const int cval = 0, & cRef = cval;
decltype(cval) x = 0; //x is const int

decltype(cRef) y = x; //y is const int& 

decltype(cRef) z;//error: z is const int& which must be initalized

use decltype for expression. decltype(r) is a reference type. but r+0 is an expression that yieds a nonreference type. Dereference operator is an example of an expression for decltype returns a reference. When we deference a pointer, we get the object to which the pointer points decltype(*p) is int& not int.

int i = 42, *ptr = & i, &ref = i;
decltype(ref + 0) b; //addition yields an int, b is int (uninitialized)

decltype(*ptr) c; //error: c is int& and must be initialized

当我们apply decltype wrap the variable’s name in one ore more parentheses((i)), compiler will evaluate the operand as an expression. A variable is an expression that can be the left-hand side of an assignment. As a result, decltype on such an expression yields a reference.

decltype((i)) d; //error: d is int& and must be initialized

decltype(i) e;//ok: e is an int (uninitialized)





3. Strings, Vectors, and Arrays

很多需要dynamic memory can use a vector or a string to manage the necessary storage. vectors and strings avoid the complexities involved in allocating and deallocating memory.

(a). Namespace using Declarations

  • std::cin use the scope operator(::) says want to use the name cin from the namespace std
  • using namespace::name : A using declaration let us use a name from a namespace without qualifying the name with a namespace_name::prefix
  • Headers should not not include using declarations. 原因: the contents of a header are copied into the including program’s text. If a header has a using declaraction. 那么every program that includes that header gets that same using declaraction. As a result, program didn’t intend to use the specified library name might encounter unexpected name conflicts.

(b). String

(1). Initialization

  • string s2(s1) s2 is a copy of s1
  • string s2 = s1s2(s1) 作用一样
  • string s3("value") s3 is a copy of string literal, not including the null(null在const char*的结尾处)
  • string s3 = "value", 与string s3("value")一样, copy of string literal
  • string s4(n,'c'), initialize s4 with n copies of character c

= 初始化叫做copy initialization, 忽略等号的初始化 是direct initialization

string s5 = "hiya"; //copy initilization 

string s6("hiya"); //direct initialization

string s7(10, 'c'); //direct initialization

(2). Getline

we can use getline function instead of >> operator. This function reads the given stream up to the end of the line (not including the newline) to store in string.

string line 
//read input a line at a time untile end-of-file

while(getline(cin,line)) // or use while(cin >> line)
    
    if(!line.empty())
        cout << line << endl;

(3). compare

  1. 如果两个strings 长度不一样 且 每个位置char都一样, 那么较短的string < 较长的
  2. 如果有位置不一样, 那么对比那个位置两个char, 根据字母表,靠前的char的string 更小
string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";
//str < phrase, phrase < slang 

(4). Adding Literals and strings

当我们用string + character literals时, 必须保证+号左右两侧至少有一个是string type

string s1 = "hello";
string s4 = s1 + ", "; // ok: adding a string and a literal 

string s5 = "hello" + ", "; // error: no string operand string

s6 = s1 + ", " + "world"; // ok: each + has a string operand string

s7 = "hello" + ", " + s2; // error: can't add string literals

//因为 s7 = ("hello" + "), " + s2; ("hello" + ") no strings

(5). Dealing with char in string

  • isalnum(c): true if c is a letter or digit
  • isalpha(c): true if c is a letter
  • iscntrl(c): true if c is a control character. A control character is a character that does not occupy a printing position on a display. 比如换行 \n
  • isdigit(c): true if c is a digit
  • isgraph(c): true if c is not a space but is printable, A graph character是可以用来打印的character, 除了空格
  • islower(c), isupper(c): true if c is a lowercase/upper letter
  • isprint(c): true if c is a printable character. printable character = space + graph character
  • ispunct(c): true if c is punctuation character (不是control character, a digit, a letter or a printable whitespace)
  • isspace(c): true if c is whitespace(i.e. a space, tab, vertical tab, return(回车符), newline(换行符), or formfeed)
  • isxdigit(c): true if c is 十六进制(hexadecimal digit)
  • tolower(c), toupper(c): 如果c是大/小写字母, 变小/大写, 否则不变

(6). Change char in string

use for loop and reference

string s("Hello World!!!");
for (auto &c : s) // for every char in s (note: c is a reference)

    c = toupper(c); 
cout << s << endl;

(c). Vector

We can define vector to hold objects of most any type. Becauses references are not objects, we cannot have a vector of references

(1). Initialization

  • vector<T>v1: vector hold objects of type T and v1 is empty
  • vector<T>v2(v1): v2 has a copy of each element in v1
  • vector<T>v2 = v1: v2 is copy of the each element in v1
  • vector<T>v3(n,val): v3 has n elements with value val
  • vector<T>v4(n): v3 has n copies of a value-initialized object(e,g, int 默认是0, string 默认是空)
  • vector<T>v5{a,,b,c...}: v5 has as many elements as initializers; elements are initialized by corresponding initializers
  • vector<T>v5 = {a,b,c}; 与上面一样

当我们提供两个数, 比如 对于vector<int>, 两个数可以表示size and value, 也可以表示size 为2的两个intial values, 用花括号或者方括号会有歧义. 这时 ()are used to construct object whereas {} is use to initialize the object, . 对于vector<string>, {}, 如果compiler 发现{} 不能initialize object, 会尝试去()来代替{}initialize

vector<int>v1(10,1); //initialize vector 10 个 1

vector<int>v2{10, 1}; //initialize vector 包括 10 和 1

vector<string>v3("hi"); //error


vector<string>v4{10}; //有10个默认的string

vector<string>v5{10, "hi"}; //有10个"hi"

//v4 and v5 the initializers can't be element initializers. 

//compiler looks for other ways to initialize the object from given values

如果用copy constructor, 需要type consistent,

vector<int>ivec;
vector<string>svec(ivec); //error

要使用size_type, 需要注明类型

vector<int>::size_type; //正确

vector::size_type; //错误

(2) Vector Grow Efficiently*

Key Concept: Vector Grow Efficiently: 定义空的vector 更有效. It is often unnecessary- can result in poorer performance—to define a vector of a specific size (例外是all elements actually need the same value). It is more efficient to define an empty vector and add elements as the values we need become known at run time.

for loop vector 不能改变vector的大小(size) (add, delete)

(d). Iterators

All of library containers have iterators. but only a few of them support the subscript operator

  • begin returns an iterator that deontes the first element. 如果object是const, 返回的是const_iterator,如果object不是const, 返回的是iterator
  • end position one past the end (尾元素的下一个位置), does not denote an element, 所以 it may not be incremented or deferenced
  • if a vector is const, 只能使用 const_iteratortype. Within a non const vector , we can use either iterator or const_iterator
  • >, >=, <, <= Iterator 比较, 如果Iterator refer的element 另一个iterator 指得element 在前面出现, 就是小于
vector<int>::iterator it;
string::iterator it2;//it can read and write

vector<int>::const_iterator it3; //it3 can read but not writer


const vector<int>cv;
auto it1 = cv.begin(); //return vector<int>::const_iterator

auto it3 = v.cbegin(); // eturn vector<int>::const_iterator

(1). Dereference and Member Access

(*it)->member is a synonym as it->member

(*it).empty() //dereference it and calls the member empty on object

*it.empty // error: attempts to fech the the member empty from iterator 

//but iterator has no member named empty

(2). Some vector Operations Invalidate Iterators

比如vector insert 也许会invalidate iterators: It is import to realize 当用iterator loop 不要add remove elements to the container which iterator refer.

(e). Arrays

  • Unlike vector, array have fixed size; we cannot add elements to an array.
  • 因为arrays have fixed size, they sometimes offer better run-time performance for specialized applications, 但是相应也损失了一些灵活性(flexibility)
  • array size是part of array’s type. The dimension must be known at compile time, which is constant expression
  • 默认值初始化会令数组含有未定义的值 (a default-initialized array of )
  • As with vector, arrays hold objects. Thus there are no array of references(array 不是objects), 但可以有reference to array.
  • When define an array, we must specify a type. We cannot use auto to deduce the type from a list of initializers
unsigned cnt = 42; //not constant expression

constexpr unsigned sz = 42; //constant expression

int arr[10]; // array of 10 ints

int *parr[sz]; // array of 42个 pointers to int 

string bad[cnt]; //error: cnt is not constant expression

string strs[get_size()]; //okay if get_size is constexpr, error otherwise

(1). Initializaing Array Element

  • 如果用list initialize the elements in array, 可以忽略维度.
    • If we omit the dimension, the compiler infers dimension from the number of initializers,
    • 如果specify a dimension, the number of initializers 不能超过 the specified size.
  • char arrays are special to have an additonal form. can initialize from a string literal and string literals end with a null character . That null character is copied into the array along with the characters in the literal. 用list initialization, 需要声明\0否则不会加上, 如果不用string literal, 会自动加上\0
  • cannot initialize an array as copy of another array. 也不能assign one array to another 有一些compiler 允许array assignment as a compiler extension. 但是最好不要用这种nonstandard方法
const unsigned sz = 3;
int ia1[sz] = {0,1,2}; //array of 3 inits with invalue 0, 1, 2

int a2[] = {0, 1, 2};  //array of 3 dimension3

int a3[5] = {0, 1, 2}; // equivalent to a[3] = {0,1,2,0,0}

string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""}

int a5[2] = {0,1,2}; // error: too many initializers

char array initialization

char a1[] = {'C', '+', '+'}; // list initialization, no null

char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null 

char a3[] = "C++"; // null terminator added automatically

const char a4[6] = "Daniel"; // error: no space for the null!

不能用copy initialize, 也不能assign array to another

int a[] = {0, 1, 2}; // array of three ints

int a2[] = a; // error: cannot initialize one array with another 

a2 = a; // error: cannot assign one array to another

(2). Understanding complicated Array Declarations

因为an array is an object, we can define both pointers and references to arrays. Define arrays that hold pointer 比较直接, defining a pointer or reference to an array 稍微复杂. 简单方法是从inside括号 from outside 再从右向左读,离变量近的类型对变量有直接影响

  • int *arrayPtr[10];, 是array, size 为10, 有10个 pointer to int
  • int (*Parray)[10];, 是pointer, 指向 size 为10 的 int array
  • int (&arrRef)[10];, 是reference, refer size 为10 的 int array
  • int *(&arry)[10] = ptrs, 是reference, refer size 为10的array, array hold pointer to int
int *arrayPtr[10]; //arrayPtr is an array of 10 个 pointers to int 

int &refs[10] = ...; //error: no arrays of references

int (*Parray)[10] = & arr; //Parray 是pointer points to an array of 10个 int

int (&arrRef)[10] = arr; //arrRef refers to an array of 10 个 ints

(3). Access the Elements

当我们use a variable to subscript an array, we should define variable to have type size_t, it defined in the cstddefheader, which is the c++ version of stddef.h header from C library. The difference between array and vector subscript 是 subscript operator[]` used in vector 只能applies to operands of vector, 而array subscript operator 是C++ 语言自己定义的

unsigned scores[11] = {};
for(auot i: scores)
    cout<<i<<endl;

(4). Pointers and Arrays

  • when we use an array, compiler 通常converts the array to a pointer
  • We can obtain a pointer to an array element by taking the address of that element using address-of operator(&); e.g. int nums[] = {1,2,3}; int * p = &nums[0];
  • when we use an object of array type, we are really using a pointer to the first element in that array, array实际上是指向第一个element的pointer, operations on pointer.
    • 当我们用array as an initializer, defined using auto, the deduced type is a pointer, not an array
    • 但是用到decltype, 还是会deduced 出array, 不会是pointer
  • 可以用 pointer initialize vector int arr[] = {0,1,2,3,4,5}; vector<int>iv(begin(arr), end(arr)); vector<int>iv2(arr + 1, arr+4);, iv2的vector分别来自arr[1], arr[2],arr[3]
string nums[] = {"one", "two", "three"}; // array of strings 

string *p = &nums[0]; // p points to the first element in nums

string *p2 = nums; // equivalent to p2 = &nums[0]

用array as intializer for auto, deduced type是pointer, not array

int ia[] = {0,1,2,3,4,5}; // ia is an array of ten ints

auto ia2(ia); //ia2 is an int* that points to the first element in oa

ia2 = 42; // error : ia2 is a pointer, can't assign an int 

auto ia3(&ia[0]); //now it's clear that ia3 has type int*

decltype(array) is array

decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9}; // decltype(ia)  is array not pointer

int i = 0, *p = &i;
ia3 = p; // error: can't assign an int* to array 

ia3[4] = i; // ok: assigns the value of i

(5). Pointers Are Iterators

Pointers to array elements (array) support the same operations as iterators on vector and string. We can use pointers to traverse the elements in an array.Array的beginend 返回的是pointer of object

int arr[] = {0,1,2,3,4,5};
int *p = arr; // p points to the first element in arr

++p; // p points to arr[1]

int *begin = begin(arr);//pointer to the first element in arr

int *last = end(arr); // pointer one past the last element in arr

(6). Pointer Arithmetic

  • 当add an integer to a pointer(array), the result is a new pointer, the new pointer points to the element the given number ahead of (or behind ) the original pointer. 当用两个pointers 相减, 得到the distance between those pointers. The pointers must point to elements in the same array
  • 我们可以比较两个指针, 当这两个指针来自于同一个数组 int * b = arr, *e = arr + 5; if(s<e)..., 如果不来自同一个数组, 不能比较, 因为比较毫无意义
  • 指针运算. The result of subtracting two pointers a library type named ptrdiff_t, like size_t, the ptrdiff_t type is a machine-specific type and is defined in the cstddef header, 因为相间可能有negative distance, ptrdiff_t is a signed integral type.
  • 可以用pointer +/- 整数, 再deference resulting pointer, int last = *(arr + 4); 括号是必须的当deference from pointer arithmetic
  • `we can use subscript operator on any pointer as long as thet pointer points to an element (or one past the last element) in an array, 可以用+/- number 用作subscript
    • Array 和 vector/string subscript operators 不同是, `the library types(vector/string) force the index with unsigned, 但是built-in subscript operator 可以是negative value(is not an unsigned type)
constexpr size_t  sz = 5;
int arr[sz] = {1,2,3,4,5};
int *ip=arr; //equivalent to int *ip = &arr[0];

int *ip2 = ip + 4;//ip2 points to arr[4], the last element in arr

int *ip3 = ip + 10;//error : arr has only 5 elements

auto n = end(arr) - begin(arr); // n is 5, the number of elements in arr

比较指针

int *b = arr, *e = arr + sz; 
while (b < e) {
    // use *b ++b;

}

int i = 0, sz = 42;
int *p = &i, *e = &sz;
while (p < e)
    // undefined: p and e are unrelated; comparison is meaningless! 

上面的也同样适用于null pointer and for pointers that point to an object that is not an array(两个指针必须指向同一个对象or 该对象下一个位置). if p is null pointer, 可以加减integral constant expression whose value is 0 to p. 我们也可以用null pointer 减去null pointer = 0

Deference

int ia[] = {0,2,4,6,8}; // array with 5 elements of type int

int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]

//括号是必须的

int last2 = *ia + 4; // = 0(ia[0]) + 4 = 4

subscript operator

int arr[] = {0,2,4,6,8};
int *p = arr+2; //p points to the third element in arr

int j = p[1]; // p[1] = *(p+1); = arr[3] = 6;

int k = p[-2]; // p[1] = *(p-2); = arr[0] = 0;

(7). C-Style Character Strings

  • C-style strings are not a type, 而是convention to use character strings. Strings that follow this convention 储存在character arrays and null terminated \0
  • C library string.h provides a set of functions 在C++中 functions 被定义在 cstring header
    • strlen(p): 返回p的长度, not counting the null
    • strcmp(p1,p2): compares p1 and p2 for equality. Returns 0 if p1 == p2, a positive value if p1 > p2, a negative value if p1 < p2
    • strcat(p1,p1) : Appends p2 to p1. Returns p1. p1必须large enough to hold result
    • strcpy(p1,p2) : copies p2 into p1. Returns p1. p1必须large enough to hold result

用strlen 必须有\0 terminate. 如果没有 null terminated, the result is undefined. The most likely effect of this is that strlen will keep looking through the memory THAT follows ca until it encounter a null character

char ca[] = {'C', '+', '+'}; // not null terminated

cout << strlen(ca) << endl; // disaster: ca isn't null terminated

Compare string: when we use an array, we really use a pointer to the first element in the array. 下面例子因为pointer do not address the same object, so the comparison is undefined. 我们可以用 strcp对比const char array的content

const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (ca1 < ca2) // undefined: compares two unrelated addresses


if (strcmp(ca1, ca2) < 0) // same effect as string comparison s1 < s2

concat: 对于string, 可以直接用加号, 但是doing the same with two arrays would be an error.. 因为expression tries to add two pointers which is illegal and meaningless. 如果largeStr不足够大, 很有可能引发error.如果我们更改largeStr, 又要重新检查它的size. Programs with suck ecode are error-prone

//对于string, 可以直接
string largeStr = s1 + " " + s2;

// disastrous if we miscalculated the size of largeStr

strcpy(largeStr, ca1); // copies ca1 into largeStr

strcat(largeStr, " ");  // adds a space at the end of largeStr

strcat(largeStr, ca2); // concatenates ca2 onto largeStr

(8). Mixing Library strings and C-Style Strings

  • initialize string from a string literal string s("Hello World")
  • string -> const char pointer: c_str() returns a a pointer to the beginning of a null-terminated character array that holds the same data in stringconst char *str = s.c_str(). It is not guaranteed to be valid indefinitely. 任何s的subsequent use 也许改变 s value 从而invalidate this array. 所以最好是先copy string 再call c_str(), 或者call c_str() copy 一份
string s = "abc";
const char* p = s.c_str();s
s = "xyz";
cout<<p[1]<<endl; //print y

(f). Multidimensional Arrays

严格讲, no multidimensional arrays in C++, 实际上是refered to arrays of arrays.

(1).Initialization

  • initialize all elements to 0 int arr[10][20][30] = {0}; 只能初始化0,不能初始化别的数
  • initialize whole array int ia[2][2] = {\{0, 1}, {2,3}}; or int ia[2][2] = { 0,1,2,3};
  • initialize 第一列的数 int ia[3][4] = {\{0}, {4}, {8}};, 只有ia[0][0] = 0, ia[1][0] = 4, ia[2][0] = 8, 其他数都是0.
  • initialize 第一行的数 int ix[3][4] = {0,3,6,9}; 只初始化第一行,剩下都是0
int ia[3][4];

// array of size 10; each element is a 20-element array whose elements are arrays of 30 ints

int arr[10][20][30] = {0}; //initialize all elements to 0

int ia[3][4] = { {0, 1, 2, 3},  // three elements; each element is an array of size 4 

    {4, 5, 6, 7},   {8, 9, 10, 11} };
//The nested braces are optional , 等于下面的

int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

// explicitly initialize only element 0 in each row, 只初始化每行第一个元素,剩下是0

int ia[3][4] = { \{ 0 }, { 4 }, { 8 } };

// explicitly initialize row 0; the remaining elements are value initialized

int ix[3][4] = {0, 3, 6, 9}; 

int (&row)[4] = ia[1]; // binds row to the second 4-element array in ia

(2). loop through array

注意外层for loop, 我们用了reference, 因为我们避免 normal array to pointer conversion.因为 auto deduced array type是pointer(指向该数组首元素的指针), 比如下面最后的例子不用reference, deduced to pointer, 无法compiled

size_t cnt = 0;
for (auto &row : ia) // for every element in the outer array

    for (auto &col : row) { // for every element in the inner array 
    
           ++cnt; 
    }

for (const auto &row : ia) // for every element in the outer array

     for (auto col : row) // for every element in the inner array, col is int

        cout << col << endl;

//上面的array 如果不用reference, auto deduced type是pointer 

for (auto row : ia) //

     for (auto col : row) // 

        cout << col << endl;

(3). Pointers and Multidimensional Arrays

  • 当程序使用多维数组名字时, 会自动将其转化成a pointer to the first element in the array
  • when you define a pointer to a multidimensional array, remember that a multidimensional array is a an array of arrays
  • iterate 时候可以用auto 去deduced array type, deduced 的是一个pointer to 最外层的指针
int ia[3][4]; //有三行, 每行有4个

int (*p)[4] = ia; //p is a pointer 指向array of 4 ints 

//上面()不能省去, int *p[4], 是array of size 4, hold int pointer

//ia指向的是第一行的array (size of 4)

p = &ia[2]; //p points to the last element in ia


//iterate

for(auto p = ia; p != ia + 3; ++p){
    //q points to the first element of an array of 4 ints, q points to an int

    //use auto to deduced array is pointer

    for (auto q = *p; q != *p+4; ++q)
        cout << * q << " ";
    cout<<endl;
}

for(auto p = begin(ia); p != end(ia); ++p){
    //q points to the first element of an array of 4 ints, q points to an int

    //use auto to deduced array is pointer

    for (auto q = begin(*p); q != end(p); ++q)
        cout << * q << " ";
    cout<<endl;
}

(4). Type Aliases Simplify

  • using int_array = int[4];
  • typedef int int_array[4];
typedef int int_array[4];
for(int_array * p = ia; p!= ia + 3; ++p)
    for(int * q = *p; q!=*p + 4; ++q)
        oout << *q <<endl;




4. Expressions

Overloading Operators: the type of operands(运算对象的类型) and the result 取决于 how the opeartor is defined. 但是the number of operands and ther precedence(优先级) and the associativity (结合律) of the operator cannot be changed (运算对象个数, 优先级, 结合律不能改变)

(a). lvalue / rvalue

  • When use an object as rvalue, We use an object value(its contents). 当we use an object as lvalue, we use the object’s identity (its location in memory).
  • 在需要rvalue时, 可以用lvalue替代; 但是当一个lvalue使用, 不能用rvalue来替代. When we use lvalue in place of an rvalue, the object’s contents(its value) are used
  • 具体使用:
    • Assignment 等号左面需要(nonconst) lvalue as its left-hand operand and yields its left-hand operand as lvalue
    • The address-of operator(&) requires an lvalue operand (运算对象), and returns a pointer to its operand as rvalue
    • Built-in dereference * and subscript operators(vector, string), iterator dereference all yield lvalue
    • Built-in 和 iterator 的 increment, decrement operators 也需要lvalue as operands
      • prefix operators (++i) return the object itself as lvalue, postfixi++ return a copy of the object’s original value as rvalue, postfix may be costly. 因为要储存一个copy 再做increment/decrement
      • decltype时候, 当我们apply decltype时候, 假如pint*, deference yields a lvaluedecltype(*p) is int&. Address-of operator yields an rvalue, decltype(&p) is int**

(b). Precedence and Associativity

when Precedence and associativity matter

int ia[] = {0, 2,4,6,8}; 

int last = *(ia + 4); //6 + 4 = 10

last = *ia + 4; //0 + 4 = 4

  • The precedence of postfix / prefix increment > deference
    • *pbeg++ is equivalent to *(pbeg++), pbeg++ increment pbeg and return a copy of previous value as rvalue
    • *++pbeg is equivalent to *(++pbeg) increment value and return as lvalue and dereference
  • dot > deference
    • (*p).size()如果我们忽略了括号, *p.size() error 因为pointer doesn’t have member size. p->size() yields an lvalue. The dot operator yields an lvalue if the object from which the member(成员所属对象) is fetched is an lvalue; otherwise the result is an rvalue.
  • The conditional operator/Assignment has fairly low precedence (比较对象优先级低). When we embed a conditional expression in a larger expression, 通常需要parathesize
  • Precedences specifies how the operands are grouped 但是 同一个expression say nothing about order of operands evaluated. 比如: int i = f1() * f2(); 我们知道f1 and f2 must be called before 乘法. 但是no way of knowing whether f1 先执行还是 f2 先执行
    • 准侧:
      1. 当不确定时, 用括号,
      2. if you change the value of an operand, don’t use that operand elsewhere in the same expression. (Exception: 有优先级存在且只用一次, *++iter, ++iter 更改了iter本身, 再用deference operator, no issue. 因为++ 先于 * 运算, 且iter就用了一次)

The precedence of postfix / prefix increment 高于deference

auto pbeg = v.begin();
// print elements up to the first negative value 

while (pbeg != v.end() && *beg >= 0)
    cout<<*pbeg++<<endl; //print the current value and advance pbeg

The conditional operator has fairly low precedence

cout << ((grade < 60) ? "fail" : "pass");  // prints pass or fail


cout << (grade < 60) ? "fail" : "pass"; // prints 1 or 0!  取决于grade < 60 is true or false

cout << (grade < 60); // prints 1 or 0,

cout ? "fail" : "pass"; // test cout and then yield one of the two literals 

// depending on whether cout is true or false


cout << grade < 60 ? "fail" : "pass"; // error: compares cout to 60

cout << grade; //返回的是cout, less-than has lower precedence than shift, so print grade first

cout < 60 ? "fail" : "pass"; // then compare cout to 60!


Assignment has low precedence, 下面例子如果不用括号, the operands to != would be evaluate first, then i = true / false

int i;
while ((i = get_value()) != 42) {
    // do something ... 
    
}

For operators that do not specify evaluation order, it is an error for an expression to refer to and change te same object. Expression that do so have undefined behavior 比如

int i = 0;
cout << i << ", " << ++i <<endl;// undefined 

//因为可能打印0, 1 or 1, 1

比如下面的, no guarantees whichfunctin are called first. 如果下面的function 是独立的 and do not affect the state of the same objects or perform IO, then it is okay. 如果functions do affect the same object(比如同一个class的不同function, 会change class state). then expression in error and has undefined behavior.

f() + g() * h() + j()

(c). Arithmetic Operators

overflow happens when a value is computed that is outside the range of values. 比如下面例子,shorts are 16 bits. maximum short is 32767. the value wrapped around. The sign bit which had been 0 is set to 1, resulting a negative value. 在其他system, the result might be different(也许会crash entirely). On many systems, there is no compile-time or run-time warning when an overflow occurs.

short val = 32767; //max value if shorts are 16 biys

val += 1; //this calculation overflows

cout << val; //print -32769

对于除法, C++11 新标准是商一律向0取整(truncate toward 0), (m/n)*n + m%n = m, 比如m,n是正数,(-m)/nm/(-n) 都等于-(m/n), (-m)%nm%(-n) 都等于-(m%n),

21%6;    /* resultis3 */    21/6; /* resultis3 */
21%7; /* resultis0 */       21/7; /* resultis3 */ 
-21%-8; /* resultis -5 */   -21/-8; /* resultis2 */ 
21%-5; /* resultis1 */      21/-5; /* resultis -4 */

Logical Operator

不可以写成 if(i<j<k) 因为i<j产生是true or false, 就变成true/false < k

(d). Assignment Operators

  • Assignment is right associative.
int ival, jval;
ival = jval = 0;//jval = 0 返回 jval 赋值给ival

int ival, *pval;
ival = pval = 0; //error: they are different type, multiple assignment must have the same type.

//and no conversion from int * to int  

//对于compound assignment, the left-hand operand is evaluated only once

+=  -=  *=  /=  %= // arithmetic operators

<<=  >>=  &=  ^= |= // bitwise operators


//对于普通的the operand evaludated twice: 第一次是右侧的expression, 第二次是赋值给左侧

a = a op b;//等同于

The problem is both left- and right-hand operands use beg and right-hand operand changes beg. The assignment is undefined. 因为更改了beg并且用了两次, 不知道是赋值时候是 *beg = 还是 *(beg+1) =, 因为不知道是赋值先*beg=toupper() 还是 *beg = *beg++

while (beg != s.end() && !isspace(*beg))
    *beg = toupper(*beg++); // error: this assignment is undefined


//可能会这样处理 or it may evaluate in some other way

*beg = toupper(*beg); // execution if left-hand side is evaluated first 

*(beg + 1) = toupper(*beg); // execution if right-hand side is evaluated first

(e). Bitwise Operators

  • There are no guarantees for how sign bit is handled, we strongly recommend using unsigned types with bitwise operators (对于unsigned 没有要求, 建议使用signed)
  • Bitwise Shift operators yield a value that is copy of left-hand operand 但是 right-hand operand must not be negative. e.g. 5<<-3 Error
  • left shift operator << insert 0-valued bit on the right. 而 right shift operator >> depends on the type of left-hand operand: 如果operand is unsigned, then operator insert 0 bits 在左侧, 如果signed, insert copies of sign bit or 0 bits 在左侧
  • Shift Operators (aka IO Operators) are Left Associative cout << "hi" << " there" << endl; 等同于 ( (cout << "hi") << " there" ) << endl;
  • The shift operators have mid level precedence: lower than the arithmetic operators but higher than the relational, assignment, and conditional operators. (位移低于算数,高于比较), 意味着我们经常需要括号去正确 group operators

Example. 用每一个bit 表示学生通过or fail 测验, 看第27名同学

unsign long quiz1 = 0;
quiz1 |= 1 << 27 //记录第27名同学通过测验

quiz1 &= ~(1 << 27) //记录第27名同学fail测验

bool status = quiz1 & (1<<27) //学生27是否通过测验

(f). Sizeof Operators

  • sizeof 返回一个类型大小 /表达式结果类型(return type)大小 所占字符数, returns the size in bytes of an expression or a type name. sizeof (type) or sizeof expr
  • sizeof is a constant expression of type size_t.
  • The operator is right associative.
  • sizeof *p, p可以是invalid
  • result applying sizeof
    • sizeof char = 1 guaranteed
    • sizeof reference returns the size of 被引用对象
    • sizeof pointer 得到指针本身空间大小 (the size neeed hold a pointer)
    • sizeof a deferenced pointer, 返回指针所指对象的所占空间小, 指针不需要有效,因为指针实际上没有真正使用
    • sizeof an array is the size of the entire array (整个数组的size of entire array). 等于 sizeof element 乘以 the number of elements. 注意sizeof does not conver the array to a pointer.
    • sizeof a string or vector该类型固定部分的大小(the size of fixed part of these types),不会计算对象中元素占用了多少空间 (does not return the size used by the object’s element)

sizeof *p; sizeof 满足右结合律 并且与 *优先级一样 , 从右向左顺序组合, 等价于 sizeof(*p)

在C++11中,可以用scope operator to ask the size of memeber of a class type. , 因为sizeof 运算无须提供一个具体对象,

Sales_data data, *p;
sizeof(Sales_data); // 存储Sales_data 类型所占空间大小

sizeof data; // size of data's type, i.e., sizeof(Sales_data)

sizeof p; // size of a pointer

sizeof *p; // size of the type to which p points, 即 sizeof(Sales_data) 

sizeof data.revenue; // size of the type of Sales_data's revenue member 

sizeof Sales_data::revenue; // alternative way to get the size of revenue

constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr2[sz]; // ok sizeof returns a constant expression

int a[] = { 1,2,3,4,5 };
cout << sizeof(a) << ", " < , sizeof(*a) << endl; //print 20, 4

(g). Comma Operators

  • comma operator 含有两个运算对象(operands), 从左向右顺序依次求值
  • guarantees the order of evaluation
  • 从左向右计算依次求值,先求左侧的表达式的值,然后将结果丢弃掉,the result of comma expression 是右侧的表达值. The result is an lvalue if the right-hand operand is an lvalue
vector<int>::size_type cnt = ivec.size(); 
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt) 
    ivec[ix] = cnt;

(h). Type Conversions

(1). Implicit Conversion

The compiler 自动转化运算对象

  • 如果算数运算或者关系运算对象有多种类型,需要准换成统一类型
    • 算数转化: 把类型转化成最宽的类型, 比如 一个类型是long double, 无关其他是什么类型,都被转化成long double. 如果有float, 那么整数型会被转化成float
    • In initializations, the initializer is converted to the type of the variable; In assignments, 右侧运算被convert to 左侧运算
    • 如果某个运算符运算对象不一致, 这些运算对象将转化成同一类型, 但是如果某个运算对象是unsigned, 结果就依赖于机器中各个整数类型相对大小了, 转化类型要么都是unsigned, 要么都是signed的
  • Integral promotions(整型提升): convert small integral types to a larger integral type(小整型变成大的整型). The types bool, char, signed char, unsigned char, short, and unsigned short are promoted to int if all possible values of that type fit in an int. Otherwise, the value is promoted to unsigned int.
  • 较大的char(wchar_t,char16_t, char32_t)提升成int, unsigned int, long, unsigned long, long long, unsigned long long, 前提是转化类型能容纳原来类型所有可能的值
  • 一个signed, 一个unsigned
    • when unsigned type 大于等于signed type , signed -> unsigned. 如果signed 是负数, 有副作用(比如 -1 会变成255)
    • when unsigned type 小于signed type , 结果依赖于机器, 如果unsigned的值都可以存入signed, 则 unsigned -> signed, 如果不能, signed -> unsigned
    • e.g. long, unsigned int, 如果int 和long 大小相同, 则long -> unsigned int. 如果long 类型占用空间大于int, unsigned int -> long
int ival = 3.541 + 3; //the compiler migh warn loss of precision

3.1415159L + 'a' // 'a' -> int(97) -> long double 

float f; char c; short s;
f + c; //c -> int -> floatt

s + c; //s -> int, c-> int 


int i; unsigned int ui; unsigned short us;
long l;  unsigned long ul; 
i + ul; //i -> unsigned long 

us + i; //根据unsigned short 和int 所占空间大小比较

ui + l;//根据unsigned int 和 long 所占空间大小比较

  • Array -> Pointer int ia[10]; int * ip = ia;
    • 当Array 用在decltype, &, sizeof 以及 typeid中不会发转化, , 或者当引用初始化数组也不会 int (&arrRef)[10] = &ia
  • Pointer Conversion: a pointer to any nonconst type can be converted to void*, any a pointer to any type can be converted to a const void *
  • Pointer -> Bool: If pointer value is zero, the conversion yield false, otherwise true char * cp = get_string(); if(cp)
  • to Const: 允许pointer to a non const type to a pointer to the const type and reference.
  • Conversion defined by Class Types: Class types 可以定义conversions that the compiler will aplly automatically. The compiler apply only one class-type conversion at a time. e.g. string s; while(cin >> s); while 把 cin 转化成bool
int i;
const int &j = i; // convert a nonconst to a reference to const int

const int *p = &i; // convert address of a nonconst to the address of a const 

int &r = j, *q = p; // error: conversion from const to nonconst 

(2). Explicit Conversion

Use cast to request explicit conversion. (有时候不得不用cast, 但是cast 这种方法本质上是非常危险的), cast 只有cast reference 不是copy, pointer, value都是copy

//const cast pointer 是copy value 
const char* cp = "123";
char * newp = const_cast<char*>(cp);
newp = "abc";
cout<< cp <<" , " << newp<<endl; //print 123, abc


double cp = 10;
double& newp = static_cast<double&>(cp);
double newp2 = static_cast<double&>(cp);
newp = 20;
newp2 = 30;
cout<< cp <<" , " << newp<<" , "<< newp2<<endl; //20, 20, 30 

static_cast is often use when a large arithmetic type is assigned to a smaller type. 一般来说 如果compiler 发现一个较大的类型试图赋值给较小的类型, 会generate warning. 但是当我们do a explicit cast, the warning message is turned off.

static cast is useful when perform a version that compiler不会自动转换的. 例如, we can use static_cast to retrieve pointer value that was stored in a void*pointer

static_cast强制转换原来类型时, 我们应该确保指针的值保持不变, 也就是说强制转化的结果与原始的地址相等. We must certain that the type to which we cast the pointer is the actual type of that pointer. If the types not match, the result is undefined

void* p = &d; // ok: address of any nonconst object can be stored in a void*

 // ok: converts void* back to the original pointer type

double *dp = static_cast<double*>(p);

const_cast : change only a low-level const in operand. cast away the const. 一旦cast away const, the compiler不会发出警告when writing to that object. If object is orginally not a const, using cast to obtain write access is legal. However using a const_cast in order to write to a const object is undefined.

const char *pc;
char *p = const_cast<char*>pc; //okay but writing through p is undefined, 因为pc是const

const char *cp;
// error: static_cast can't cast away const

char *q = static_cast<char*>(cp);
string txt = static_cast<string>(cp); // ok: converts string literal to string 

string txt = const_cast<string>(cp); // error: const_cast only changes constness

reinterpret_cast: generally performs a low-level reinterpretation of the bit pattern of its operands. 为运算对象的位模式提供较低层次上的重新解释

int *ip;
char *pc = reinterpret_cast<char*>(ip);
string str(pc); //result in bizarre run-time behavior.

//pc指的真实对象是int 而非char. 如果把pc当成普通char指针,就会发生running error

上面例子提供了使用reinterpret_cast非常危险, 类型改变了,但compiler没有任何警告或者错误的信息提示. 比如上面中pc实际指向int *, 但使用pc时候, 会认定它是char*类型. compiler没法知道它存放是指向int 的指针

When to use reinterpret_cast: when interface with opaque data types(frequently occurs in vendor APIs over which programmer has no control). 下面例子是where a vendor provides an API for storing and retrieving arbitrary global data:

// vendor.hpp

typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

static_cast won’t work, must use reinterpret_cast

//main 

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

nt main() {
    MyUserData u;

    // store global data
    
    VendorGlobalUserData d1;
 //  d1 = &u;                                          // compile error

 //  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error

    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    
    VendorSetUserData(d1);
        // do other stuff...

        // retrieve global data
	
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
 //  p = d2;                                           // compile error

 //  p = static_cast<MyUserData *>(d2);                // compile error

    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

old style cast

type (expr); // function-style cast notation 

(type) expr; // C-language-style cast notation

char *pc = (char*) ip; // ip是指向整数的指针

//效果与使用reinterpret_cast一样





5. Statements

The null statement is a empty statment ;

while(iter != svc.end()); //while 循环主体

    ++iter; //iter is not part of the loop, run after while

Dangling(悬垂) else: 规定了else与最近的if匹配, 比如下面的else 初衷是为跟外层的if 匹配,但是却跟内层匹配, 如果想让与外层if 匹配,需要花括号

if (grade > 6)
    if (grade  > 10 )
        cout <<" excellent";
else //与  if (grade  > 10 ) 匹配,实际上是6 < grade < 10进入这个else

//而不是grade < 6进入else

    cout << "fail"

Do while: 不要在条件部分定义变量, 然后在while 中判断

do{
    int i = 1;
}while( i != 1) // error: declaration in a do condition

Break/continue 只作用于最近的for, while, switch

switch(buf[0]) { 
    case '-':
        for (auto it = buf.begin()+1; it != buf.end(); ++it)
        {
            if (*it == ' ')
                break;//作用于for, 不会break case

        } 

Goto:

  • 与switch 类似, 不可以定义初始化的值 然后带出scope (cannot transfter control of initialized varaible 从初始化的scope到variable被用的scope)
  • A jump backward over an already executed definition is okay(跳回一个已经执行的定义是ok). Jumping back to a point before a variable is defined destroys the variable and constructs it again
    goto end;
    int ix = 10; // error: goto bypasses an initialized variable definition

end: 
    // error: code here could use ix but the goto bypassed its declaration 

    ix = 42;

begin:
    int sz = get_size(); 
    if (sz <= 0) {
        goto begin;
    }
// Here sz is destroyed when the goto executes. 

//It is defined and initialized 一个新的sz when jump back to begin

(a). switch

  • switch 中的case, switch 必须是integral constant expression (整型常量表达式), 所以不能用string, double 作为switch 或者case 条件, 可以用simple char(not char*, not char array), int, enum, string[index] (it’s char)
  • 任何两个case 不能相同
  • 如果没有break, 当一个case match, 会执行接下来所有的case until program explicitly interrupts it, 但是最后一个case 不一定加break, 但为了安全最好加上, 如果有新的case, 这样不用担心前面break
  • 有时候想两个或多个case 值共享一组操作(common set of actions), we omit a break, allowing program to fall through multiple case labels
  • default: 如果没有任意match上, 进入default, 如果default 是空的, 加上default 也是有用的,因为告诉读者我们考虑了全部情况
  • Variable Definitions inside the Body of a switch:
    • 如果定义并初始化的值, it is illegal to bring to outscope
    • 如果定义没有初始化,是可以的;
int ival = 42;
char ch = getVal();
switch (ch){
    case 3.14: //error noninteger as case label

    case ival: // error nonconstant as case label

}

string a = "123";
switch(a) // error not integral constant expression

stack several case together with no break.只要ch是元音,都执行相同代码

unsigned vowelCnt = 0; 
switch (ch) {
    // any occurrence of a, e, i, o, or u increments vowelCnt

    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
        ++vowelCnt;
        break;
}

//因为case 之后不一定要换换换行

// alternative legal syntax

switch (ch)
{
    case 'a': case 'e': case 'i': case 'o': case 'u':
        ++vowelCnt;
        break;
}

当omit break时候, 会执行所有的case, 比如ch = 'e', eCnt, iCnt, oCnt, uCnt, d, 都会加1

switch (ch) {
    case 'a':
        ++aCnt; // oops: should have a break statement

    case 'e':
        ++eCnt; // oops: should have a break statement

    case 'i':
        ++iCnt; // oops: should have a break statement

    case 'o':
        ++oCnt; // oops: should have a break statement

    case 'u': 
        ++uCnt;

    default:
        d++;
}

可以在别的case 中定义variable, 在别的case中使用; 但是不可以在别的case中 定义并初始化值, 然后在别的scope 使用

case true:
// this switch statement is illegal because these initializations might be  bypassed

    string file_name; // error: implicit初始化, file_name = ""

    int ival = 0;// error: control bypasses an explicitly initialized variable

    int jval; // ok: because jval is not initialized
    
    break; 

case false:
    // ok: jval is in scope but is uninitialized
    
    jval = next_num(); // ok: assign a value to jval

    if (file_name.empty()) // file_name is in scope but wasn't initialized

(b). Range for statment

for (declaration : expression)
     statement

expression must represent a sequence(必须是一个序列), 例如 a braced initializer list({1,2,3}), an array, or an object such as vector or string (has iterator)

(c). Exception Handling

  • when throw error, need to initialize error by give a string or c-style character style
  • Each exception classes define a member function named what:takes no arguments and return c-style character style (即 const char*), 返回值是copy of string used to initialize the object; 对于没有初始值的exception,what returns 由compiler 决定
  • If no appropriate catch is found, execution is transferred to a library function terminate. 该函数行为与系统有关 but is guaranteed to stop further execution of the program
try{
    throw runtime_error("Data must refer to same object");
}catch(runtime_error err){
    cout <<err.what(); //print Data must refer to same objec

}
  • exception header defines 最通用的 exception class, 它只报告exception occur 但没有额外信息
  • stdexcept header files 几个general-purpose exception classes exception, runtime_error,range_error, overflow_error, underflow_error, logical_error, domain_error, invalid_argument, length_error(试图建立一个超出该类型最大长度的对象), out_of_range
  • new header defines the bad_alloc exception type
  • type_info header defines the bad_cast exception type
  • 只能用default initialization exception, bad_alloc, bad_cast 不允许提供initializer for these exception types




6. Functions

(a). Function Basic

Calling a Function. A function does 2 things. 1. Initializes the function’s parameter from corresponding argument, and it transfer control to function. 2. Execution of the calling function(主调函数) is suspended and execution of the called function begins(被调函数, 函数主题)

当function 执行完成: the return statement does 2 things. 1. return the value in the return 2. transfers control out of the called function back to the calling function

Parameters and Arguments(形参和实参)Arguments are the initializers for a function’s parameters. e.g. int fact(int val); fact(5), 5 是argument and val是parameter

Function Parameter List: A function’s parameter list 可以为空, 但不能省略. Parameter names are optional. However, there is no way to use an unnamed parameter. 如果parameter unnamed, 表示function 没有使用它,但是called 时候不代表argument 会减少

void f1(){ /* ... */ } // implicit void parameter list

void f2(void){ /* ... */ } // explicit void parameter list

Function Return Type: return type can be void. 但是不能是array type, IO type, or function type., 但是可以return a pointer to array or a function.

local variable: 如果局部变量和外面变量名字一样, 局部变量会hide declarations of the same name made in outer scope

automatic objects: object exist only while a block is executing. 在执行完block, value of automatic objects 被销毁, the values of automatic objects are undefined.

local static objects: is intialized when the first time execution pass through object definitions. 直到program terminate destoryed. 如果不提供初始值, 将执行value initailized 意味着local statics of built-in type are initialized to 0 (内置类型的静态 被初始化为0)

size_t count_calls(){
    static size_t ctr = 0; //value will persist across calls

    return ++ctr;
} 

Function Declarations: 可以被declared (声明)多次, 但只能定义一次. 声明不需要函数主体, 用一个分号代替即可(也可以省略parameter的名字) void pint(vector<int>&li);

These 3 elements—return type, function name, and parameter types—describe the function’s interface. They specify all the information we need to call the function. Function declarations are also known as the function prototype.

(b). Argument Passing

(1). Pointer Parameter: nonreference type, when we copy pointer, the value of the pointer is copied. After the copy, two pointers are distinct(pointer地址不同的, 但指向相同的对象, which means 如果更改function 内部的pointer 指代对象, 不会影响外面的pointer). , 因为pointer give indirect access the object, 所以可以通过指针修改object的值

//case 1: pointer is different 

void reset(int *ip){
    int j = 10;
    ip = &j;
}

int i = 0, *p = &i;
reset(p);
cout << *p << " , "<<i<<endl; //print 0, 0 


//case 2: 可以改变object的值

void reset(int *ip){
    *ip = 10;
}
int i = 0, *p = &i;
reset(p);
cout << *p << " , "<<i<<endl; //print 10, 10 

(2) Reference Parameter: Can use Reference Parameters to return additional information

(3). const Parameters and Arguments: when we copy an argument to initialize a parameter. top-level const are ignored. 顶层const被ignore We can pass either a const or a nonconst object to a parameter that has a top-level const, Error to write functions, only parameter top-level const不同

因为顶层const 被忽略掉, 所以两个fun, 两个reset function是一样的function

void fcn(const int i) // 可以pass int or const int 

void fcn(int i) //也可以接受int, const int, compiler报错already has a body

void reset(int const * const ip); 
//可以pass const int * p, 也可以是pass const int * const p

void reset(int const * ip);
//是两个一样的reset function

//下面两个function 不一样, 因为不是top-level const

void fun(const int & i);
void fun(int & i);

(4). Use Reference to const When Possible:如果只pass reference without const,可能给读者错误导function可能改变value; 另外用reference, we cannot pass a const object, a literal, or an object requires conversion to a plain reference parameter

find_char(string& str, char ch);
void isSentence(const string & s){
    return find_char(s, 'a'); //error 因为不能pass const reference to reference 

}

(5). Array Parameters

数组有两个性质: 1. cannot copy an array 2. when use array, it convert to a pointer. 因为不能拷贝, cannot pass by value. 因为array convert to pointer, 当pass array, we actutally pass a pointer to the array’s first element.. 当call function时, array size is irrelevant

下面是几个函数declarations are equivalent; 每一个function 都是有一个parameter of type const int*, 如果都在一个程序中写出下面三个function, compiler会显示redefintion error.

// each function has a single parameter of type const int*

void print(const int*);
void print(const int[]); //可以看出来, 函数意图是作用于数组

void print(const int[10]); //这我们期望有多少个元素, 实际不一定

int i = 0, j[2] = {0, 1};
print(&i); // ok: &i is int*

print(j); // ok: j is converted to an int* that points to j[0],

//the size of array is irrelevant

function parameter 是 array, size doesn’t matter

void print(int arr[3]){
   cout << arr[0]<<endl;
}

int a [6] = {0,1,2,3,4,5};
print(a); //print 0

因为arrays are passed as pointer, functions 通常不知道size of the array, caller必须提供额外的信息

方法一:Using a Marker to specify the extent of an Array. e.g. C-style character strings 有null character at the end. 缺点是: not work well that don’t have end-marker like int

void print(const char *cp)
{
    if (cp) // if cp is not a null pointer character

        while (*cp) // so long as the character it points to is not a null

            cout << *cp++; // print the character and advance the pointer

}

方法二: Using the Standard Library Conventions, pass pointers to the first and one past the last element in the array.

void print(const int *beg, const int *end)
{
    while (beg != end)
        cout << *beg++ << endl; // print element and advance pointer

}

int j[2] = {0,1};
print(begin(j), end(j));

方法三: Explicitly Passing a Size Parameter: which is common in C and older C++ programs. The function executes safely as long as the size passed is no greater than the actual size of the array.

void print(const int ia[], size_t size)
{
    for (size_t i = 0; i != size; ++i)
        cout << ia[i] << endl;
}

intj[]={0,1}; 
print(j, end(j) - begin(j));

(6).Array Reference Parameters 只有需要change the value, 采用Array reference, 如果不改变value, 用 pointer to const, 当有引用时, 只能传入与引用size 一样维度的array,size matters, 比如下面的function, 只能传入size = 10的, 只有当reference 传入时候才能用for_range loop 因为function pass的是array, 不是reference传入的 function pass的是pointer

Array 被当做reference 传入 不会转换为pointer type

void print(int (&arr)[10])
//括号不能少

//int &arr[10]: error, reference is not object, 不能定义array of reference

{ 
    for (auto elem : arr) 
        cout << elem << endl;
}

int i = 0, j[2] = {0, 1};
int k[10] = {0,1,2,3,4,5,6,7,8,9};
print(&i);// error: argument is not an array of ten ints 

print(j); // error: argument is not an array of ten ints 

print(k); // ok: argument is an array of ten ints

(7). Passing a Multidimensional Array: 实际上没有真正的多维数组. Multidimensional array is passed as a pointer to its first element (因为时多维数组, the first element 还是 a pointer to the first element). The size of the second (and any subsequent) dimension is part of the element type and must be specified (second dimension size matters)

void print(int (*matrix)[10], int rowSize) { /* . . . */ }
//declares matrix as a pointer to an array of ten ints.

// equivalent definition

void print(int matrix[][10], int rowSize) { /* . . . */ }

(8). main: Handling Command-Line Options: argv 第一个元素指向程序名字或者空的字符串, 接下来的传入argument provided by command line. 最后一个element is guaranteed to 0.

int main(int argc, char *argv[]) 
//也等于

int main(int argc, char **argv) 

//如果传入 prog -d -o ofile data0

argv[0] = "prog"; argv[1] = "-d"; argv[2] = "-o";
argv[3] = "ofile"; argv[4] = "data0"; argv[5] = 0;

(9). Functions with Varying Parameters: 处理不同数量argument: 如果argument类型相同, we can pass initializer_list的标准库, 如果函数类型不同, we need to a special kind of function (variadic template). 还有一个parameter type (ellipsis) 可以用于传入varying number of arguments. (注意ellipsis should be only used in programs that need to interface to C functions)

An initializer_list is a library type that represents an array of values of the specified type. This type is defined in the initializer_list header.

  • unlike vector, element in initializer_list are always const values, we cannot change the elements in initializer_list
  • When we pass a sequence of values to an initializer_list parameter, we must enclose the sequence in curly braces{}
Syntax Description
initializer_list<T>lst Default initialization, an empty list of elements of type T
initializer_list<T>lst{a,b,c} elements are copied of correspoding initializers. elements in the list are const
lst2(lst) copy or assigning an initailizer_list 但不会 一 一拷贝列表中元素. After the copy, the original and the copy share he elements. shallow copy
lst.size() number of elements in list
lst.begin() / lst.end()) returns a pointer to the first / one past the last element in lst
initializer_list<string> ls; // initializer_list of strings 

initializer_list<int> li; // initializer_list of ints

void error_msg(ErrCode e, initializer_list<string> il)
{
    cout << e.msg() << ": ";
    for (auto beg = il.begin(); beg != il.end(); ++beg) 
        cout << *beg << " " ;
}

error_msg(ErrCode(42), {"functionX", expected, actual});

Ellipsis Parameters(省略符形参): Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs. 不能被用于其他目的. Ellipsis只能用于types ftht are common to both C and C++(object of most class types are not copied properly when passd to an ellipsis parameter)

An ellipsis parameter 只能出现在parameter最后一个, and may take either of two forms

void foo(parm_list, ...);
void foo(...);

check it

(c). Return Types

Failing to provide a return after a loop that contains a return is an error(for loop 中有if 然后return, 但是程序fail 进入那个if,所以程序最后到达end of function without return). However, many compilers will not detect such errors.

(1). How Values Are Returned: The return value is used to initialize a temporary at the call site(调用点), and that temporary is the result of the function call.

  • 如果return是value (returns are rvalue), return value is copied to the call site. The function returns a copy of he return value.
  • 如果return是reference(returns are lvalue), reference is just another name for the object to which it refers

当function return 是 list initializing the return value, element inside brace list cannot have larger type which require narrowning conversion

vector<int>get(){
    return {3.14,5};//3.14->int illegal, 

}

(2). Never Return a Reference or Pointer to a Local Object: after a terminate, reference to local objects refer to memory that is no longer valid.

const string & manip()
{
    string ret;
    if (!ret.empty()) return ret; 
    // WRONG: returning a reference to a local object! 

    else return "Empty";
    //WRONG: "Empty" is a local temporary string

}

因为function reference 返回时lvalue, we can assign to the result of a function that returns a reference to nonconst

char &get_val(string &str, string::size_type ix)
{ 
    return str[ix];
}
string s("a value");
get_val(s, 0) = 'A'; // changes s[0] to A 

cout << s << endl; // prints A value return 0;

(3).Functions That Return Class Types and the Call Operator: call operator(call function() )的优先级和 dot and arrow operator 一样, call operator is left associative(assignment 是right associative). 所以if function returns a pointer, reference or object of class type, we can use the result to call a member of the resulting object

auto sz = shorterString(s1, s2).size();

(4). Return from main: The main function is allowed to terminate without a return. 如果control reaches the end of main and there is no return, compiler implicitly inserts a return of 0. main的返回值看做状态指示器, 返回0表示执行成功, 返回其他值表示执行失败. 非0值得含义由机器而定. 为了使返回值与机器无关, cstdlib header中定义了2个预处理(preprocessor)变量 that we can use to indicate success or failure, 因为是预处理变量, 不能加上std::, 也不能using 声明中出现. 注: main function may not call itself

int main()
{
    if(some_failure)
        return EXIT_FAILURE; // defined in cstdlib

    else  
        return EXIT_SUCCESS; // defined in cstdlib
         
}

(5). Return a Pointer to an Array: 因为不能copy an array, a function cannot return an array. However, a function can return a pointer or reference to a array

方法一: 为了straightforward, 可以用type alias

typedef int arrT[10]; //arrT is a synonym for the type array of 10 ints

using arrtT = int[10]; //跟上面一样的

arrT* func(int i); //returns a pointer to an array of 10 ints

without type alias,想定义一个函数 that returns a pointer to an array, dimension(维度)必须跟在函数名字之后, However, function parameter list which also follows the name. Parameter list 在dimension 前面. function 两端的括号必须在, 如果没有括号表示returns an array of pointers 但是array不能被copy

Type (*function(parameter_list))[dimension]
//Dimension 表示数组的大小

//function 两端的括号必须在

int (*func(int i)) [10]; 
//function take a integer 

//返回a pointer to array of 10 ints

方法二: Using a Trailing Return Type: 在C++11中, 另一种简化function returns a pointer to array 是 using a trailing return type. A trailing return types可以用于任何function, but are most useful for functions with complicated return types(such as pointers to array). Parameter list 在->左侧, 为了表示函数真的返回类型跟在parameter list 之后, we use auto for return type

// fcn takes an int argument and returns a pointer to an array of ten ints

auto func(int i) -> int(*)[10];

auto func(int i) -> string{
    return "val";
}

方法三: Using decltype:

int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) *arrPtr(int i)
{
    return (i % 2) ? &odd : &even; // returns a pointer to the array 
    
}

注: decltype 并不负责把array 转化成pointer type. The type returned by decltype is an array type, 我们必须加上* to indicate that arrPtr returns a pointer

(d). Overloaded Functions

  • Functions有一样的名字但是parameter lists different and appear in the same scope are overloaded. When call these functions, compiler can deduce which function we want based on argument type we pass
  • main function 不能被overloaded
  • It is an error for two functions only return types different(如果只有返回类型不同,error)
  • It is an error for two functions only parameter top level const different(如果一个function 有顶层const, 另一个没有顶层const, 两个function 是一样的)
Record lookup(const Account&);
bool lookup(const Account&);//error: only return type is different

Determine whether two parameter types differ

// each pair declares the same function 每一组function 都是一样的

Record lookup(const Account &acct);
Record lookup(const Account&); // parameter names are ignored


typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&); // Telno and Phone are the same type

顶层const 不影响传入函数的对象, 一个拥有顶层const和一个没有top level const的function 无法区分, 下面每组的function 第一个和第二个声明是一样的

Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)


Record lookup(Phone*);
Record lookup(Phone* const); // redeclares Record lookup(Phone*)

但如果const时候底层的, we can overload 如果指针or 引用 指向const or non const object. 下面的例子, compiler can use the constness of the argument to distinguish which function to call 因为const 不能转化类型, 所以只pass 给含有const 的function. 但是nonconst 可以转化成const,所以下面四个function 都可以被nonconst function call. 但是compiler prefer the nonconst version when we pass a nonconst object or pointer to nonconst

// functions taking const and nonconst references or pointers have different parameters

// declarations for four independent, overloaded functions

Record lookup(Account&); // function that takes a reference to Account

Record lookup(const Account&); // new function that takes a const reference

//当Account是const call 第二个,当Account 不是const call


Record lookup(Account*); // new function, takes a pointer to Account

Record lookup(const Account*); // new function, takes a pointer to const

const_cast

//需要返回一个引用, 如果直接定义 string& shorterString(const string &s1, const string &s2)

//会报错,不可以把 const reference assign 给 reference, 需要const_cast 

const string &shorterString(const string &s1, const string &s2) {
    return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2)
{
    auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

首先cast argument to references to const. function returns a reference to const string, 再cast const string & back to plain string&

calling an overloaded function

当call overload function, 有三种可能的结果:

  • The compiler finds exactly one function that is a best match for the actual arguments and generate code to call that function
  • 找不到任何一个函数match the arguments in the call, 此时compiler 发出error message no match
  • 如果有大于一个function matches and none of the matches is clearly best, 也发发生错误 ambiguous call.

Overloading and Scope: In C++ name lookup happens before type checking . 当declare a name in an inner scope, that name hides use of that name declared in outer scope.

下面例子, when call print, the compiler 首先 look for declaration of print, once the name is found. Compiler 会忽略掉外层scope 一样的命名的function, Instead, the copiler assumes that 在当前作用域中找到的就是the one for the name we are using

string read();
void print(const string &);
void print(double); // overloads the print function 

void fooBar(int ival)
{
bool read = false; // new scope: hides the outer declaration of read 

string s = read(); // error: read is a bool variable, not a function 

// bad practice: usually it's a bad idea to declare functions at local scope

void print(int); // new scope: hides previous instances of print 

print("Value: "); // error: print(const string &) is hidden 

print(ival); // ok: print(int) is visible

print(3.14); // ok: calls print(int); print(double) is hidden

}

(e). Features for Specialized Uses

(1). default Arguments: are used for the right-most(trailing) argument of a call. 设计时让不怎么可能用default放前面, likely to use a default value appear first

string screen(int ht = 24, int wid = 80, char backgrnd = ' ');
window = screen(, , '?'); // error: can omit only trailing arguments

另一种用default parameter的形式, names used as default arguments are resolved in the scope of function delcaration(名字在函数声明所在作用域内解析), The value that those names represent is evaluated at the time of the call.(求值过程发生在函数调用时)

int wd = 80;
char def = ' ';
size_t ht = 5;
string screen(int = ht, int = wd, char = def);

void screen(int a1 = ht, int a2 = wd, char a3 = def) { //与上面function是一样的

	cout << a1 << " , " << a2 << " , " << a3; //print 5, 80 , *
}

void f(){
    def = '*';  //change value of default argument

    int wd = 100; //hides the outer definition of wd but does not change the default

    string window = screen(); // calls screen(ht(), 80, '*')
    
}

(2). Inline and constexpr Functions

比如我们有个比较string 大小的function, 好处是如果我们修改比较方法,直接修改function, 而不用找比较表达式所有出现的地方修改conditon. 潜在缺点是: slower than evaluating the equivalent expression. 因为call function does a lot of work: 调用前先保存寄存器(registers) , 返回时恢复. 可能需要copy argument and program branches to a new location(程序转向一个新的位置继续执行)

inline vs constexpr

  • inline functions, expression are always evaluated at the run time and are request to compiler to expand(展开function) at compile time . 但是constexpr functions are evaluated at compiled time(not always)
constexpr long int fib(int n) 
{ 
    return (n <= 1)? n : fib(n-1) + fib(n-2); 
} 
  
int main () 
{ 
    // value of res is computed at compile time.  
    
    const long int res = fib(30); 
    cout << res; 
    return 0; 
} 

const long int res = fib(30);  // run time is 0.0003s;

long int res = fib(30); //run time is 0.017s;

inline function 在每个调用点上”内联”的展开 (expanded “in line” at each call) 比如下面函数, would expand during compilation into something in line, 消除了函数运行时的开销(The run-time overhead of making shortString a function is removed)

cout << shortString(s1,s2)<<endl;
//在编译过程中展开成类似于下面的形式

cout << (s1.size() < s2.size() ? s1 : s2 )<<endl;
  • The inline specification is only a request to the compiler. The compiler may choose to ignore this request.(inline 是向compiler 发送请求, compiler可以选择忽略这个请求)
  • inline meachanism 用于规模小的, 流程直接, 频繁调用的函数, 不支持recursion(optimize small, straight-line functions that are called frequently. Many compilers will not inline a recursive function.) 也不支持很多行的函数(比如一个75行的函数很难被inline expanded)
  • inline function defintion 最好放进header, 因为inline function 和 class compilation 需要在一个translation unit. Function 放进header must marked inline 否则every translation unit which includes the header will contain a definition of the function. linker will complain about multiple definition (违反了one defintion rule)

constexpr function:

  • return type must be literal type (int(reference, pointer), double, enum, char, char pointer, 但string 不是)
  • 函数主体有且只有一个return statement(function body must contain exactly one return statement, 不能有通过if 判断的多个statement),C++ 14 allows more than one statements.
  • constexpr function can only call other constexpr function not simple function
  • constexpr function should not be void type and some operator like prefix increment (++v) are not allowed in constexpr function.
  • compiler will replace a call to constexpr funcion with its resulting value.(compiler 把constexpr 函数的调用换成其结果值)
  • constexpr functions are implicitly inline
  • A constexpr function body 可以含有other statements 只要这个statement generate no actions at run time (e.g. null statement, type aliases, and using declaractions)
  • A constexpr function is not required to return a constant expression.
  • 如果需要constexpr function 返回constant expression, 需要function argument也是constant expression
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // ok: foo is a constant expression

//compiler can verify new_sz return a constant expression at compile time

只要argument(cnt) 是constant expression, scale(arg) is a constant expression, 2 是constant expression, 所以compiler 会把所有的scale(2) 用constexpr function resulting value 代替. 如果返回不是constant expression, compiler 会有error message

constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }

int arr[scale(2)]; // ok: scale(2) is a constant expression

int i = 2; // i is not a constant expression

int a2[scale(i)]; // error: scale(i) is not a constant expression

(3). Aids for Debugging

assert is a preprocessor macro(预处理器).

  • assertpreprocessor macro.
    • A preprocessor macro is a preprocessor variable that acts like inline function.
    • 预处理器名字 由预处理器(preprocessor) 管理而不是compiler, 所以当使用preprocessor name directly, do not provide a using declaration or std:: for them.
    • 和preprocessor variable 一样, macro names must be unique within the program. Program that include cassert header 不能有variable, function, or other entity named assert.. 很多header 都包含了cassert header, 所以即使没有include 它,也很有可能直接用
  • assert(expr); in cassert header 中: evaluates expr and if expression is false, 输出信息并终止程序的执行, 如果表达式正确 什么也不做,
    • assert 常用来检查不能发生的情况, assert(word.size() > threshold);
  • assert 用来当做调试程序的辅助手段(aid) 而不能用作真正的run-time logic checks or error checking

the preprocessor defines four other names that can be useful in debugging:

Preprocessor variables Description
_ FILE _ string literal containing the name of the file
_ LINE _ integer literal containing the current line number
_ TIME _ string literal containing the time the file was compiled
_ DATE _ string literal containing the date the file was compiled
if (word.size() < threshold)
    cerr << "Error: " << _ _FILE_ _
    << " : in function " << _ _func_ _
    << " at line " << _ _LINE_ _ << endl << " Compiled on " 
    << _ _DATE_ _ << " at " << _ _TIME_ _ << endl

// print 

// Error: wdebug.cc : in function main at line 27 

//    Compiled on Jul 11 2012 at 20:50:03

NDEBUG Preprocessor Variable

  • assert的行为depends on the status of a preprocessor variable 名为 NDEBUG, 如果NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined. So by default, assert performs a run-time check
  • We can turn off debugging by providing a #define to define NDEBUG.
    • 也有个compiler provide a command-line option that lets us define preprocessor variable CC -D NDEBUG main.cpp or /D with microsoft compiler has the same effect as writing define NDEBUG at the beginning of main.cpp
  • 定义了NDEBUG 能避免各种条件所需的runtime 开销 (avoid run-time overhead involved in checking various conditions). no run-time check
  • 如果NDEBUG is not defined, the code between the ifndef NDBUG and endif is executed. 如果NDEBUG is defined, code is ignored.
void print(const int ia[], size_t size)
{
#ifndef NDEBUG

// _ _func_ _ is a local static defined by the compiler that holds the function's name

// _ _func_ _  是局部静态变量,显示函数名字

cerr << _ _func_ _ << ": array size is " << size << endl; 

#endif

(f). Function Matching

  • step 1: 选定a set of overloaded functions considered for the call. The function in this set are the candidate functions
    • candidate functions 是 function has the same named as the called function and declaraction is visibe at the point of the call 下面例子中有4个候选的function
  • step 2: 根据arguments,从candidate function 中选出可以被arguments调用的函数, 这些新选出的函数称为viable function. 下面例子有两个viable functions. To be viable function,:
    • must have the same number of parameters as there are arguments int the call (parameter 数量必须跟调用的函数提供argument 数量一样)
    • 每个argument must match or be convertible to corresponding parameter
    • If there are no viable functions, the compiler will complain that there is no matching function.如果没有找到可行函数, compiler 会报错
  • step 3: find the best match: 这一过程是逐一检查argument in the call and 选择function parameter 与argument best match的viable function, 下面例子中当 call f(int) 需要double -> int, 而call f(double, double)不用convert
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14); 
f(5.6); // calls void f(double, double)

当有多个函数比配: compiler以此检查argument by argument, 如果有一个function 满足下列条件,则比配成功:

  • 每个argument的匹配不劣于其他可行函数需要的匹配 (The match for each argument is no worse than the match required by any ohter viable function)
  • 至少有一个arugment的match 优于其他的viable functions的匹配(There is at least one argument for which the match is better than the match provided by any other viable function)
  • 如果没有任何一个函数脱颖而出, 则call is error, the compiler will complain that the call is ambiguous
    • 比如f(42,2.56), f(int, int);f(double, double);是一样的, ambigous call.
  • 如果需要cast 才能找到最匹配的call, 是poorly design. Casts should not be needed to call an overloaded function. The need for a cast suggests that the parameter sets are designed poorly.

compiler ranks the conversions that convert each argument to the type of parameters:

  1. 精准匹配(exact match). An exact match happens when:
    • argument and parameter types are identical
    • The argument is converted from an array or function type to the corresponding pointer type.
    • top-level const is added or discarded from the argument (添加或去掉top-level const)
  2. Match through a const conversion (通过const 转化实现)
  3. Match through a promotion (通过类型提升实现, e.g. short->int)
  4. Match through an arithmetic or pointer conversion (通过算数或者指针转换 (e.g. pointer -> bool if(*cp) ))
  5. Match through a class-type conversion.

注:好的desgin 一般不会有functions with parameters 类似下面例子的

small integral types always promote to int or to a larger integral type e.g1: 一个function take parameter int, 另一个take short. Short 只有argument 是short的时候会被call, 即使有时是很小的整数值, 也会被promote to int. whereas calling the short version would require a conversion.

所有arithmetic conversion 都是一个级别的, 从int 到unsigned int 转换不比从int 到double 转换级别高

void ff(int); 
void ff(short);
ff('a'); // char promotes to int; calls f(int)


void manip(long);
void manip(float); 
manip(3.14); // error: ambiguous cal

Function Matching and const Arguments:

the compiler uses the constness of the argument to decide which function to call: 下面第二个例子, b to initialize a reference to either const or nonconst type.但是initializing a reference to const from a nonconst object require a conversion. 但是convert to const的级别低于exact match, 因此non-const version is prefer

pointer works in similar ways, 如果argument is a pointer to const, the call will match function that takes const *, otherwise, 如果argument is a pointer to nonconst, the function 有 a plain pointer is called

Record lookup(Account&); // function that takes a reference to Account

Record lookup(const Account&); // new function that takes a const reference


const Account a;
Account b;
lookup(a); // calls lookup(const Account&)

lookup(b); // calls lookup(Account&)

(g). Pointers to Functions

  • a pointer that denotes a function rather than an object. Like any other pointer, a function pointer points to a particular type.
  • 函数类型(type) is determined by its return type and the types of it parameters. 与函数名无关(function name is not part of its type)
  • we can directly use function pointer without dereference operator
  • There is no conversion between pointers to one function type and pointers to another function type.(不同的函数指针之间没有转换规则)
  • 可以assign nullptr or zero valued integer constant expression to a function pointer 表示function pointer 没有指向任何的function
  • function parameter can convert function to function pointer, 但是function return cannot convert function to function pointer

function pointer 括号(*pf) 必不可少, 否则的话是 function returns bool pointer

bool lengthCompare(const string &, const string &);
// pf points to a function returning bool that takes two const string references 

bool (*pf)(const string &, const string &); // uninitialized


pf = lengthCompare; // pf now points to the function named lengthCompare 

pf = &lengthCompare; // equivalent assignment: address-of operator is optional

pf = 0; // ok: pf points to no function


//三个等价调用

bool b1 = pf("hello", "goodbye"); // calls lengthCompare 

bool b2 = (*pf)("hello", "goodbye"); // equivalent call

bool b3 = lengthCompare("hello", "goodbye"); // equivalent call


string::size_type sumLength(const string&, const string&); 
bool cstringCompare(const char*, const char*);
pf = cstringCompare; // error: parameter types differ 返回类型不匹配

对于overloaded function,上下文必须指出选用哪个函数

void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff; // pf1 points to ff(unsigned)

double (*pf3)(int*) = ff; // error: return type of ff and pf3 don't match

Function Pointer Parameters: we can write parameter that looks like a function, 但实际上是被treated as pointer.当pass a function as an argument, 它将自动convert to a pointer

// third parameter is a function type and is automatically treated as a pointer to function

void useBigger(const string &s1, const string &s2,
    bool pf(const string &, const string &));//it is a pointer

    
// equivalent declaration: explicitly define the parameter as a pointer to function

void useBigger(const string &s1, const string &s2,
    bool (*pf)(const string &, const string &));

可以用typedef 来避免冗长(tedious), FuncFunc2是函数类型, FuncPFuncP2是pointer类型,注 decltype returns the function type; 不会自动转换成指针类型,如果我们想要pointer, 必须加上*

// Func and Func2 have function type, 是函数类型

typedef bool Func(const string&, const string&); 

typedef decltype(lengthCompare) Func2; // equivalent type


typedef bool(*FuncP)(const string&, const string&);

typedef decltype(lengthCompare) *FuncP2; // equivalent type


// equivalent declarations of useBigger using type aliases

void useBigger(const string&, const string&, Func); 
void useBigger(const string&, const string&, FuncP2);

Returning a Pointer to Function: 和数组类似,虽然不能返回函数, 但可以返回函数的指针, we must write the return type as a pointer type, compiler will not 自动treat a function return type as pointer type(和function parameter 不一样)

int (*f1(int))(int*, int); f1 is a function 返回的是pointer,返回的有parameter list, so pointer points to a function and that function returns an int

auto f1 (int) -> int(*)(int*, int); 可以用trailing return to make it clear

using F = int(int*, int); // F is a function type, not a pointer 

using PF = int(*)(int*, int); // PF is a pointer type

PF f1(int); // ok: PF is a pointer to function; f1 returns a pointer to function 

F f1(int); // error: F is a function type; f1 can't return a function

F *f1(int); // ok: explicitly specify that the return type is a pointer to function

Using auto or decltype for Function Pointer Types

如果我们知道函数返回是哪个一个, 可以用decltype简化书写. 需要注意的是, when we apply decltype to a function, it returns a function type, not a pointer to function type. 需要add * to indicate that we are returning a pointer not a function

string::size_type sumLength(const string&, const string&); 
string::size_type largerLength(const string&, const string&);

//根据parameter, getFcn返回是pointer to largerLength or sumLength

decltype(sumLength) *getFcn(const string &);




7. Classes

The fundamental ideas behind classes are data abstraction and encapsulation*(数据抽象和封装). Data abstraction is a programming (and design) technique that relies on the separation of interface and implementation. Encapsulation enforces the separation of a class’ interface and implementation, A class that is encapsulated hides its implementation. 一个user只能看见interface, 不能看见implementation

Benefit of Encapsulation:

  1. User code cannot inadvertently corrupt the state of an encapsulated object.用户不会无意间破坏封装对象
  2. The implementation of an encapsulated class can change over time without requiring changes in user-level code. 可以随时改变implementation without 影响用户

(a). This & Const

Functions defined in the classes are implicitly inline. 定义在class 内部的函数都是inline的. function声明必须在函数内部声明, we can define a member function’s body either inside or outside of the class body.

Ordinarily, nonmember functions that are part of the interface of a class should be declared in the same header as the class itself. (比如下面的print, read 不是class member 也应该与class 放入一个header)

struct Sales_data {
    std::string isbn() const { return bookNo; }//定义声明在内部 

    //下面两个function, 声明在struct内部, 但是定义在外部
    
    Sales_data& combine(const Sales_data&); 
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0; 
    double revenue = 0.0;
  };

// nonmember Sales_data interface functions, 定义声明都在外部

Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&); 
std::istream &read(std::istream&, Sales_data&);

this:

  • this is a const pointer, we cannot change the address that this holds. this is a constant pointer that holds the memory address of current object. this is not available in static member function
  • By default, the type of this is const pointer to the non const version of the class type(top level const, lower level nonconst).
    • *thisreference to object, 而不是const reference to object, 因为this没有lower constness
  • Member functions access the object through extra, implicit parameter named this. this is initialized with the address of the object which the function (只限于nonstatic) was invoked.
    • 比如 total.isbn() compiler passes the address of total to the implicit this parameter. compiler 会等价的rewrite 为 Sales_data::isbn(&total)(调用Sales_data的isbn的成员时传入total 的地址)
  • 在class 函数内部的直接access member of the object, 因为any direct use is assumed to be an implicit reference through this(任何对类成员直接访问都被看作this的隐式引用)
    • bookNo as if this->bookNo
  • It’s illegal to define a parameter or variable named this.

const Member Functions

  • const is to modify the type of the implicit this pointer, 表示 this is a pointer to const. Member function that use const被称为const member functions
    • const member functions cannot change the object on which they are called. 因此const function 不能write to data members of the objects 只能read
  • const function call 总结:
    • 不能在const function中call nonconst member function, 只能call const 的function. 因为Cannot bind this(this 没有low level const) to a const object (this没有lower const, lower const -> no lower const的 不可以) .
      • 比如const A a; a.get();//error when get is not const function
      • 所以把不改变object 的function 设置成const, 可以提高函数灵活性
    • const object 只能call const function 不能call nonconst function, Objects that are const, and references or pointers to const objects, may call only const member functions.
  • const member function that returns *this as a reference should have a return type const class &., 不能返回nonconst reference (因为this is a const reference, cannot convert to a const reference to nonconst reference ).
    • 但是如果不是返回this, 不必是const reference, 比如可以返回普通reference like int&(但若外面接用non const reference, 不能更改这个int, 因为是const function返回值, 不能更改object state)
  • 可以overload function 基于是不是const (match rule 跟function overloading match rule 类似)
    • const object 只能call const function, 不能call nonconst function
    • nonconst object 可以call non-const 也可以call const version, 但是nonconst version 是better match(因为exact match 优于const version)

Defining a Function to Return “This” Object: 下面例子total 被绑定到this, rhs 被绑定到trans上,

Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold; 
revenue += rhs.revenue; 
 return *this; // return the object on which the function was called

//use this to access the object as a whole

}

total.combine(trans); 
  • inline member functions should be defined in the same header as the corresponding class definition
  • mutable Data Members: a mutable data member is never const even when it is a member of a const object(即使是const 对象成员,也不是const), a const member function of class may change a mutable member

下面初始化Screen, class 内部初始化要么使用= 在class内部初始化, or the direct form of initialization using {}

class Screen{
public: 
    typedef std::string::size_type pos;
    Screen() = default; // needed because Screen has another constructor 
    
    // cursor initialized to 0 by its in-class initializer

    Screen(pos ht, pos wd, char c): height(ht), width(wd), contents(ht * wd, c) { }
    //定义在class内部函数, inline, we don't need to specify inline, 但也可以specify


    char get() const // implicitly inline 定义在class 内部都是inline (implictly)

        { return contents[cursor]; } 

    inline char get(pos ht, pos wd) const; // explicitly inline

    Screen &move(pos r, pos c); // can be made inline later

private:
    pos cursor = 0;
    pos height = 0, width = 0; std::string contents;
    mutable size_t access_ctr; // may change even in a const object / const member function

}

std::vector<Screen> screens{Screen(24, 80, ' ') };

如果moveset 返回都是class的reference(*this),

inline Screen &Screen::set(char c)
{
    contents[cursor] = c; 
    return *this;
} 

//则可以用

myScreen.move(4,0).set('#');

如果moveset 返回都是value, 而不是reference

Screen temp = myScreen.move(4,0)
temp =  temp.set('#');

const function不能返回 nonconst的reference. const function内不能call non const function

//error

Screen& display(cout) const {
   return (*this);
}

//correct

const Screen& display(cout) const {
   return (*this);
}

Screen myScreen;
// if display returns a const reference, the call to set is an error 

myScreen.display(cout).set('*');

Const function overload, object 是不是const, 决定call 哪个display function.. 下面nonconst display 把 *this pointer implicit convert from Screen * const to const Screen * const

class Screen { 
public:
// display overloaded on whether the object is const or not 

Screen &display(std::ostream &os)
    { do_display(os); return *this; } 
const Screen &display(std::ostream &os) const
    { do_display(os); return *this; }

private:
// function to do the work of displaying a Screen

void do_display(std::ostream &os) const {os << contents;} //inline implicitly 

//call这个function, 不会带来额外的开销(no run-time overhead)

};

(b), Friend and Encapsulation

A class may contain zero or more access specifiers(private, public), and there are no restrictions on how often an access specifier may appear.

The only difference between using class and using struct to define a class is the default access level and 继承是public and private.

定义private encapsulation 的优点:

  • 通过定义data private, 作者可以自由修改数据,只要interface 不变,用户代码不变,但是如果the data are public, then any code that used the old data members 也许brokern. 需要rewrite any code that relied on old representation 后才能使用
  • 另一个定义private 优点.data are protected from mistakes that users might introduce, 防止因为用户原因数据被破坏, 如果发现bug corrupt object state, 发现bug位置是本地, 就去implementation差错, 而不用看用户的code, 降低了维护的难度

friend:

  • A class can allow another class or function to access its nonpublic members by making that class or function a friend.
  • Friend declarations may appear only inside a class definition. 友元声明只能在class 内部 (好的编程习惯是: 集中在程序开始或者结尾集中定义友元)
  • 如果Friend function定义在class 内部是inline function
  • friend declaraction 仅仅specifies access, 而不是general declaration of the function, 如果我们想要users able to call friend function. usually declare each friend (outside the class) in the same header as the class itself.
    • 一个友元的outside declaration 必须在call 友元 的member function前面, 所以用到友元的function 最好放在class 外面定义 (some compilers do not enforce the lookup rules for friends)

friend function 最好定义在class 外面, 定义在内部, 有的compiler 也可以pass, 算是declaration. Class Friend Function 不能通过class access

class Derived  {
protected:
   int j = 5;
public:
   friend void get(Derived& d) {
      cout << d.i << endl;
   }
};
Derived i;
d.get(i);//error 

get(i); //okay

Friend Class:

  • friendship is not transitive, 比如下面例子中 假如Window_mgr有自己的friends, those friends 没有access to Screen
  • 还用只声明一个class 的function 有friend 权限, Making a member function a friend 需要仔细设计程序, 比如下面例子中, clearWindow_mgr member 但用到 Screen 的 member
    • 首先定义class Window_mgr. 其中声明但不定义clear functinon,
    • 再定义class Screen, including a friend declaration for clear
    • 最后define clear 此时才可以使用Screen的成员
  • Classes and nonmember functions need not have been declared before they are used in a friend declaration. When a name first appears in a friend declaration, that name is implicitly assumed to be part of the surrounding scope
class Screen{
    friend class Window_mgr; 
    //Window_mrg可以access Screen Class的所有数据


    friend void Window_mgr::clear(ScreenIndex); 
    //只声明class 一个clear function 有friend 权限, 但是clear需要declare before Class Screen 

}

下面函数

extern std::ostream& storeOn(std::ostream &, Screen &);//声明成友元

extern BitMap& storeOn(BitMap &, Screen &); 
//接受BitMap & 的storeOn, 对Screen 没有访问权限对private / public

class Screen {
// ostream version of storeOn may access the private parts of Screen objects

friend std::ostream& storeOn(std::ostream &, Screen &);
};

friend function scope

struct X {
    friend void f() { /* friend function can be defined in the class */ }
    
    X() { f(); } // error: no declaration for f;

    void h();
};
void X::g() { return f(); } // error: f hasn't been declared

void f(); // declares the function defined inside X 

void X::h() { return f(); } // ok: declaration for f is now in scope

(c). Class Scope

Class Types:

  • Every class defines a unique type, 即使两个different types define the same members 他们也是不同类型的.
  • 可以只声明class 而不定义class, like class Screen;, 这种声明叫做forward declaration, class is considered declared (not yet defined) as soon as its class name has been seen. 但在它声明之后 定义之前是一个incomplete type, 因为不清楚它包含哪些成员
    • imcomplete type(只是这个class, 不算class内数据) can be used in limited ways: define pointers or references to such types, 也可以定义functions that use an incomplete type as a parameter or return type
    • 但是 a class cannot have members of its own type
  • A class 必须被defined 而不仅仅是声明before we creats objects of that type, acccess member type by using reference or pointer. 因为compiler 需要know storage such objects need, 还 need to know 它有什么member
  • 数据访问: class 外部访问public data/function,只能通过object, reference, pointer; type members(typedef) from the class 可以访问 using the scope operator.
  • 在class 外部定义function,
    • 需要provide class name :: function name. function内就可以随便用class member without using class name (因为everyname is seen in class scope)
    • function return type 声明在 class name 前, so return type is outside scope, 因此return type需要specify class name::, 如果return type is defined inside class
struct First { int memi;
int getMem();
};
struct Second {
int memi;
int getMem();
};

First obj1;
Second obj2 = obj1; // error: obj1 and obj2 have different types

Sales_data item1; // default-initialized object of type Sales_data class 

class Sales_data item1; // equivalent declaration

数据访问, return type (如果定义在class内) not in class scope, 需要class name + scope operator

class Window_mgr { 
public:
    // add a Screen to the window and returns its index 

    ScreenIndex addScreen(const Screen&); 
};
// return type is seen before we're in the scope of Window_mgr

Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s)

(1).name lookup: the process of finding which declarations match the use of a name

  1. Look for declaration of the name in the block (before use of name) which the name was used. 只在名字出现在块中(使用之前的)查找
  2. 如果名字没有发现, look in the enclosing scope(继续查找外层作用域)
  3. if no declaration found, program is error

class defintion are processed in order of :

  1. memeber declarations are compiled (先编译成员声明)
  2. function bodies/definitions are processed and compiled only after the entire class has been seen (直到class全部可见后,编译函数体)
  • 因为函数体直到class 可见后 才compiled. 所以function 可以用任何name defined inside the class. 假如function definition 和member declaration 同时进行, function只能用已经被看见的名字
  • 上面的two-step process 只适用于member function bodies, Names used in declarations, 包括了return type and types in parameter list 必须被看见before they are used. 否则会报错
  • tips:: 定义type names (typedef, using) 通常在class beginning, 这样any member that uses that type will be seen after the type name has been already been defined

balanceMoney 来自于enclosing scope(外层作用域), return 的 bal 来自class内部而不是string

typedef double Money; 
string bal;
class Account { 
    public:
        Money balance() { return bal; }
    private:
        Money bal; 
};

(2).Type Names Are Special: 一般来说, inner scope can redefine a name from an outer scope (out scope names is hidden). 但是对于type 不行, class inner scope cannot redefine a typename 如果这个type 被member function parameter/return 使用(typedef redefine 可以在VS中, 但不适用于linux)

typedef double Money;
class Account {
public:
    Money balance() { return bal; } 
    // uses Money from the outer

private:
    typedef double Money; // error: cannot redefine Money

    Money bal;
};

比如下面的例子, in class and outside 都有typedef, 但是function 对同一个typedef用到不同的type, extremely confusing, 所以standard bans it

using Foo = int;

struct X {
    Foo a;    // ::Foo, i.e., int
    
    void meow() { 
        Foo b = a; // X::Foo; error: no conversion from int to char*
	
    }
    using Foo = char*;
};

(3). Normal Block-Scope Name Lookup inside Member Definitions

A name used in the body of a member function is resolved as follows:(一般不建议use the name of another member as the name for function parameter, 不建议使用成员的名字用作函数参数)

  1. 首先在member function内部寻找(在name 被使用之前部分)
  2. 如果没有找到, 再到class 内部继续寻找declaration. All the members of the class are considered (class所有成员都被考虑)
  3. 如果没有找到, look for a declaration that is in scope before the member function defintiion. (在函数定义前的作用域, 因为函数可能定义在class外部(in file) )

当compiler 处理dummy_fcn 的乘法时, 首先寻找 height in the scope of that function. 从parameter中找到

// note: this code is for illustration purposes only and reflects bad practice

// it is generally a bad idea to use the same name for a parameter and a member

int height; // defines a name subsequently used inside Screen

class Screen {
public:
    typedef std::string::size_type pos; 
    void dummy_fcn(pos height) {
        cursor = width * height; // which height? the parameter 
        
    }
private:
    pos cursor = 0;
    pos height = 0, width = 0;
};

上面例子中height 隐藏了member named height. 如果我们想override the normal lookup rules, 可以如下, 如果想使用outer class 外层作用域的, 可以用scope operator ::

//Case 1: class member height

// bad practice: names local to member functions shouldn't hide member names 

void Screen::dummy_fcn(pos height) 
{
cursor = width * this->height; // member height 

// alternative way to indicate the member

cursor = width * Screen::height; // member height

}

//Case 2: use class member 

// good practice: don't use a member name for a parameter or other local variable 

void Screen::dummy_fcn(pos ht) {
cursor = width * height; // member height 

}

//Case 3:  Use global

// bad practice: don't hide names that are needed from surrounding scopes 

void Screen::dummy_fcn(pos height) {
cursor = width * ::height;// which height? the global one 

}

注意 verify is not visible before the definition of the class screen. 但是第三步lookup includes the scope where member definition befores. 因为verifysetHeight前定义,因此找到

int height; // defines a name subsequently used inside Screen 

class Screen {
public:
    typedef std::string::size_type pos;
    void setHeight(pos);
    pos height = 0; // hides the declaration of height in the outer scope

};
Screen::pos verify(Screen::pos); 

void Screen::setHeight(pos var) {
// var: refers to the parameter

// height: refers to the class member

// verify: refers to the global function 

    height = verify(var);
}

(d). Constructors

Constructors

  • constructor run whenever an object of a class type is created.只要class的对象被创建, 就会执行constructor
  • Unlike other member functions, constructor 不能被declared as const. When we create a const object of a class type, the object 直到constructure completes the object’s initialization 才获得constness 属性 . constructors initialize const objects during their construction.
  • Classes control default initialization by defining a special constructor, known as the default constructor: is one that takes no arguments.
    • default constructor 是constructor work without any argument(either no parameters, or all parameters have default values); 错误的概念是constructor with no parameters; 比如dog(string name = “Bob”);
    • 如果没有explicitly define any constructors, compiler will implicitly define and generate synthesized default constructor, 对于大多数class, this synthesized constructor initializes each data member as follows:
      • 如果there is an in-class initializer, 用default constructor 用初始值初始化成员 比如class 中string a = "dog";
      • Otherwise, default-initialize the member,比如class 中string a; 默认初始化为空
      • 如果不支持默认初始化, your default constructor should use the constructor initailizer list to initialize every member of the class (e.g. sale(): a(0) {})
  • constructor 不应该override in-class initializers except to use a different initial value.

Some Classes Cannot Rely on the Synthesized Default Constructor

  • 只有我们没有声明任何的constructor,compiler 才会generates synthesized default constructor, 如果有自己定义constructor, 除非自己定义default constructor, 否则不会生成
  • 第二原因是: synthesized default constructor does the wrong thing, 比如数组或者指针对象被默认初始化, 他们值是未定义的
    • Classes that have members of built-in or compound 只当有 in-class initializers 才 should rely on the synthesized default constructor。
  • 有时候compiler is unable to systhesize one. 比如一个class has member 没有default constructor, or const, reference 没有in-class initializer

library 比如vector or string, compiler generated 的 copy, assignment, destructor works correctly, 因为the vector class takes care of copying or assigning the element. 当object is destroyed, the vector member is destroyed which 反过来destroys the elements in vector.

(1). Constructor Initializer List

Assignment 和 initialization 是不同的: 在class 中, Assignment是先初始化 再赋值, initialization是直接初始化, 比如下面例子, How significant the distinction between initialization and assignment 由data member 类型决定

//is Initialization

class data{
    double revenue = 0.0; 
    string bookNo; 
};

//Is Assignment

class data{
    double revenue; 
    string bookNo;
    data(const string & a, double price){
        bookNo = a;
        revenue = price;
    } 
};
  • 必须用constructor initializer list to provide values for members that are const, reference, or a class type that does have a default constructor,const, reference 是必须initialized的
    • 如果member 有 const or reference, 不会有default constructor, 必须要Initialized using constructor initialization list
  • class members are initialized 与他们在class 出现的顺序一致, constructor initializer list order 不会影响他们实际初始化的顺序
    • 一般顺序不matter, 但是如果一个成员用另一个成员初始化后的值, order 就matter了 (有些compiler 会有warning)
    • 最好write constructor initializer in the same order as members are declared in class. 其次避免using members to initialize other members
class ConstRef { 
public:
    ConstRef(int ii); 
private:
    int i, &ri;
    const int ci;
};

// ok: explicitly initialize reference and const members 

ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { }

Order of Initialization, 下边例子中, constructor initializer listj is initialized then i. 但实际上是i先initialized, 但是当initialized 时 with the undefined value of j

class X{
    int i;
    int j;
public: 
    // undefined: i is initialized before j

    X(int val): j(val), i(j){}
};

class want{
public:
	int a;
	int b;
	want(int x_, int y_) : b(x_), a(b) {}
};


list<int> l;
want a(10, 5);
cout << a.a << " , " << a.b << endl; //print 0, 10 

(2) Delegating Constructor(委托构造函数):

  • A delegating constructor uses another constructor from its own class to perform its initialization
  • In a delegating constructor, the member initializer list has single entry to call the same class another constructor.
  • When a constructor delegates to another constructor, 先run delegated-to constructor 之后才把control returned to the function body of delegating constructor
class Sales_data {
public:
    // nondelegating constructor initializes members from corresponding arguments 
    
    Sales_data(std::string s, unsigned cnt, double price):
            bookNo(s), units_sold(cnt), revenue(cnt*price) {}

// remaining constructors all delegate to another constructor

Sales_data(): Sales_data("", 0, 0) {} 
Sales_data(std::string s): Sales_data(s, 0,0) {} 
Sales_data(std::istream &is): Sales_data() {read(is, *this); }
};

(2) Role of Default Constructor:

The default constructor is used automatically whenever an object is default or value initialized. Default initialization happens:

  1. when we define nonstatic varaibles or array without initializers
  2. When a class that itself has members of class type uses the synthesized default constructor
  3. When members of class type are not explicitly initialized in a constructor initializer list

Value initialization happens:

  • Array Initilization, 当提供的初始值 fewer than its size
  • when we define a local static object without an initializer
  • 当我们书写T()的表达式explicitly request value initialization where T is the name of a type (比如vector constructor takes a single argument to specify vector’s size)
class NoDefault { 
public:
    NoDefault(const std::string&);
    // additional members follow, but no other constructors 

};

struct A { 
    NoDefault my_mem;
};
A a; // error: cannot synthesize a constructor for A 


struct B {
    B() {} // error: no initializer for b_member 
    
    NoDefault b_member;
};

错误declare object

Sales_data obj(); // error:  declare a function, not an object

//表示a function taking no parameters and return type is Sales_data

Sales_data obj;

(3). Implicit Class-Type Conversions

  • constructor that can be called with single argument defined an implicit conversion (有时候叫 converting constructors)
    • 定义了conversion from constructor’s parameter type to class type.
  • 只允许一步conversion, 比如constructor take a string parameter, 我们不可以用const char* 到string 再到class type
  • explicit: prevents implicit conversion. explicit meaningful only on constructor that can be called with a single constructor..
    • explicit is used only on constructor declaration inside the class. It is not repeated on a definition outside class body.
    • 对于constructor that require more arguments 不会perform implicit conversion,所以没有用explicit的必要性
    • explicit 只能用于direct initialization, 不能用于copy initialization
    • compiler will not do implicit conversion for explicit, but we can use 有explicit constructor to force a conversion (比如static_cast)

e.g. string -> Sales_data, compiler automatically creates a temporary Sales_data object from string then pass to combine. 因为combine take const reference, we can pass tempoary object

struct Sales_data {
    Sales_data(std::string s): Sales_data(s, 0,0) {} 
    Sales_data(std::istream &is): Sales_data() {read(is, *this); }
    Sales_data& Sales_data::combine(const Sales_data &rhs)
    {
        units_sold += rhs.units_sold; 
        revenue += rhs.revenue; 
        return *this; // return the object on which the function was called 
		
    }
  };

string null_book = "9-999-99999-9";
item.combine(null_book);

// error: requires two user-defined conversions: 

// (1) convert "9-999-99999-9" to string

// (2) convert that (temporary) string to Sales_data 

item.combine("9-999-99999-9");

//one step implicit type conversion 

// ok: explicit conversion to string, implicit conversion to Sales_data

 item.combine(string("9-999-99999-9"));
 // ok: implicit conversion to string, explicit conversion to Sales_data 
 
 item.combine(Sales_data("9-999-99999-9"));

explicit

class Sales_data { 
public:
    Sales_data() = default;
    Sales_data(const std::string &s, unsigned n, double p):
        bookNo(s), units_sold(n), revenue(p*n) { } 
    explicit Sales_data(const std::string &s): bookNo(s) { } 
    explicit Sales_data(std::istream&);
};

item.combine(null_book); // error: string constructor is explicit 

item.combine(cin); // error: istream constructor is explicit

explicit 只用于declaration inside class, not for definition outside class

// error: explicit allowed only on a constructor declaration in a class header 

explicit Sales_data::Sales_data(istream& is)
{ read(is, *this);}

explicit 只能用于direct initialization, not for copy initialization

Sales_data item1 (null_book); // ok: direct initialization

// error: cannot use the copy form of initialization with an explicit constructor 

Sales_data item2 = null_book;

可以用 有explicit constructor to force conversion, static_cast to perform an explicit, rather than an implicit conversion. 用static_cast uses the istreamconstructor to construct temporary Sales_data object

// ok: the argument is an explicitly constructed Sales_data object 

item.combine(Sales_data(null_book));
// ok: static_cast can use an explicit constructor

item.combine(static_cast<Sales_data>(cin));

(4). Aggregate Classes

An aggregate class gives users direct access to its members and has special initialization syntax. A class is an aggregate if

  • All of its data members are public (所有public)
  • It does not define any constructors (没有定义任何constructor)
  • It has no in-class initializers (没有class 内部数据初始值)
  • It has no base classes or virtual functions. (没有base class 和 virtual)

An aggregate class 可以:

  • An aggregate class can define member functions
  • An aggregate class can overload operators.

Initialization:

  • we can initialize the data members of an aggregate class by providing a braced list of member initializers
  • Initializer的order 必须与declaration of data members 一样
  • 如果提供的elements are fewer than class members, the trailing members are value initialized

Aggregate class 的Initialization Drawbacks:

  1. Requires that all data members of class be public
  2. 将正确初始化的重任(burden)给了user. error-prone, 因为用户容易忘记值, 或者提供一个不正确的初始值
  3. If a member is added or removed, all initialization have to be updated.

Example of aggregate class

struct Data{
    int ival;
    string s;
}

// val1.ival = 0; val1.s = string("Anna") 

Data val1 = { 0, "Anna" };

(5). Literal Classes

  • A literal type is a type that can qualify as constexpr. This is true for scalar types, references, certain classes, and arrays of any such types.
    • scalar type 包括了
      • arithmetic (integral, float)
      • pointers: T * for any type T (比如pointer to a class is scaler type 但是这个class 本身不是scalar type)
      • enum
      • pointer-to-member (object pointer, function pointer, nullptr_t)

literal clasess 标准 if it is :

  • a scalar type or
  • a reference type or
  • a array of iteral type or
  • a class types of following properites
    • all of its non-static data members and base classes are of literal types.
    • it is an aggregate type or has at least one constexpr constructor (至少有一个class type) or constructor template that is not a copy or move constructor, and
    • every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression
    • it has a trivial destructor (default destructor 自己不定义destructor or use keyword default)


  • 如果是aggregate class, 则class data members are of literal type is a literal class.
  • 如果 是nonaggregate class, 则需要满足下列要求:
    • data members all must literal type
    • The class must have at least one constexpr constructor
    • If a data member has an in-class initializer, the initializer for a member of built-in type must be a constant expression. 如果不是built-in type, the initializer must use the member’s own constexpr constructor.
    • The class must use default definition for its destructor (default destructor), which is the member that destroys objects of the class type.

constexpr parameter 和 return type 必须是 literal type. class that are literal type 也许有funcition members that are constexpr(需要meet all requirements of constexpr function, 这些function 也是implicitly const)

constexpr Constructor

  • constexpr constructor can be declared as = default (or = delete)的形式.
  • 如果没有用=default, 需要meet requirements of constructor(no return statment) and constexpr function(the only executable statement it can have is return statment(only one return)), 所以通常上body of constexpr constructor (body) is empty
  • constexpr constructor must initialize every data member. The initializers 必须either constexpr constructor or a constant expression
  • A constexpr constructor is used to generate objects that are constexpr and for parameters or return types in constexpr functions(用于生成constexpr对象以及constexpr 函数的参数或返回类型)
class Debug { 
public:
    constexpr Debug(bool b = true): hw(b), io(b), other(b) {}
    constexpr Debug(bool h, bool i, bool o):
        hw(h), io(i), other(o) {}
    constexpr bool any() { return hw || io || other; } 
    void set_io(bool b) { io = b; }
    void set_hw(bool b) { hw = b; }
    void set_other(bool b) { hw = b; }
private:
    bool hw; // hardware errors other than IO errors 
    
    bool io; // IO errors

    bool other; // other errors
};
constexpr Debug io_sub(false, true, false); // debugging IO

if (io_sub.any()) // equivalent to if(true)

    cerr << "print appropriate error messages" << endl;

constexpr Debug prod(false); // no debugging during production 

if (prod.any()) // equivalent to if(false)

    cerr << "print an error message" << e

Definitions of constexpr constructors must satisfy the following requirements(from IBM):

  • The containing class must not have any virtual base classes(used in virtual inheitance).
  • Each of the parameter types is a literal type.
  • Its function body is = delete or = default; otherwise, it must satisfy the following constraints:
    • It is not a function try block.
    • The compound statement in it must contain only the following statements(除了return的语句可以是):
      • null statements
      • static_assert declarations
      • typedef declarations that do not define classes or enumerations
      • using directives
      • using declarations
  • Each nonstatic data member and base class subobject is initialized.
  • Each constructor that is used for initializing nonstatic data members and base class subobjects is a constexpr constructor. Initializers for all nonstatic data members that are not named by a member initializer identifier are constant expressions.
  • When initializing data members, all implicit conversions that are involved in the following context must be valid in a constant expression:
    • Calling any constructors
    • Converting any expressions to data member types

(e). Static Members

  • static member can be public or private. The type of static data member can be const, reference, array, class type.
  • private static member 只能被member function call, 可以用来初始化static variable(见下面例子),不能被outside class用
  • static members of a class exist outside any object.Object do not contain data associated with static data members. static member functions 不与对象绑定(bound)在一起. 只能是单向的: 从normal function 中call static varaibles/function 不可以从static function中call normal varaibles
    • static member function 不能declared as const function.
    • static member functions don’t have *this pointer.不能用*this在static member 中, This restriction适用于explicit use of this and implicit use of this by calling a nonstatic member.
    • static function 中不能call nonstatic variable 或者 nonstatic function
    • 但member function 可以用static function/variable (without scope operator)
  • can use scope operator to access static member Account::initRate, 也可以通过object, reference, pointer of class type to access
  • member function can use static member directly without scope operator
class Account{
public:
   void calculate() { amount += amount * interestRate; }
   static double rate() { return interestRate; }
   static void rate(double);
private:
   std::string owner;
   double amount;
   static double interestRate;
   static double initRate();
   //银行利率 需要apply给所有user, 用static
   
};
double r;
Account ac1;
Account *ac2 = &ac1;
// equivalent ways to call the static member rate function

r = ac1.rate(); // through an Account object or reference

r = ac2->rate(); // through a pointer to an Account object

Define Static Members

  • 因为static data members 不是 part of objects of class type, 他们并不是在create object是被定义的. They are not initialized by class constructor,
  • 不能initialize static member inside class, 必须define and initialize static data member outside class body
  • 和其他object一样,static data member只能被定义一次 (好习惯是 把所有static member的definitions 和所有noninline member functions的定义放在一起)
  • 可以define static member function 在class内部或者外部.如果在外部define, 不用加上 static keyword

下面例子initialize static interestRate例子, Once the class name Account is seen, we can use initRate without scope operator as the initializer for rate. Note 尽管initRate is private, 可以用它initialize interestRate.

//不需要static keyword to define static member

void Account::rate(double newRate)
{
interestRate = newRate;
}

//initialize static data member

double Account::interestRate = initRate();

In-Class Initialization of static Data Members

  • 通常static members不能intialized in class body. 但可以为static members 提供 const integral type的in-class initialzers, 不过必须要求static members必须是 constexpr of literal type.
    • initializer 必须是constant expression
  • 如果某个static member 仅限于compiler可以替换它的值, then 一个初始化的const or constexpr static 不需要分别定义. 如果将它用于值不能替换的场景中, then 该成员必须有一个定义语句
    • 例如下面例子中, 用到period的地方仅仅是daily_tbl, there is no need to define period outside class Account, 但是程序细微的改变不能造成无法编译, 因为找不到该成员定义. e.g. pass period to a function that takes a const int& then peiord must be defined
  • 如果an initializer provided inside class, member definition outside class 不能specify an initial value
  • 好的编程习惯是: Even if a const static data member is initialized in the class body, that member ordinarily should be defined outside the class.
class Account {
public:
   static double rate() { return interestRate; }
   static void rate(double);
private:
   static constexpr int period = 30;// period is a constant expression

   double daily_tbl[period];
};

// definition of a static member with no initializer

constexpr int Account::period; // initializer provided in the class

static Members Can Be Used in Ways Ordinary Members Can’t

  • 因为static member 是不跟object 绑定, 所以static data member 可以是incomplete type. static data member 可以是the same type as the class type, A non-static member 不可以这样declared, 只可以declare reference or pointer to an object of its class
  • we can use static member as default argument. (非static data member不能被使用为default argument 因为its value is part of the object, 看见parameter时, class declaration 还没有完成, so is an error)
class Bar {
private:
   static Bar mem1; // ok: static member can have incomplete type
   
   Bar *mem2; // ok: pointer member can have incomplete type
   
   Bar mem3; // error: data members must have complete type
   
};

拿static member as default argument

class Screen {
public:
// bkground refers to the static member

// declared later in the class definition

   Screen& clear(char = bkground);
private:
   static const char bkground;
};




8. IO Library

(a). IO Classes

Header Type
iostream istream, wistream reads from a stream
ostream, wostream writes to a stream
iostream, wiostream reads and writes a stream
fstream iftream, wifstream reads from a file
ofstream, wofstream writes to a file
fstream, wfstream reads and writes a file
sstream istringstream, wistringstream reads from a string
ostringstream, wostringstream writes to a string
stringstream, wstringstream reads and writes a string

为了支持wide characters,library defines types and objects 用来操作 wchar_t data. e,g, wcin,wcout, wcerr. wider character types and objects 定义在same header, 所以比如 fstreamifstream, 也有wifstream.

  • ifstream and istringstream inherit from istream, 因次可以像使用istream对象一样使用ifstreamistringstream, 同样, ofstream and ostringstream inherit from ostream , 所以他们用cin, cout的方法都是一样的,

(1). No Copy or Assign for IO objects

  • 因为IO 不能符号或者赋值, 所以 不能有parameter or return type 是 IOStream types. 对IO操作的function 通常通过reference的方式pass and return
  • 因为Reading or writting IO object change its state, 因此reference must not be const
ofstream out1, out2;
out1 = out2; // error: cannot assign stream objects

ofstream print(ofstream); // error: can't initialize the ofstream parameter

out2 = print(out2); // error: cannot copy stream objects

(2). Condition States

  • 因为IO操作会可能发生错误, 一些错误是可以恢复的, 而另一些错误已经到了系统深处(deep within the system),已经超过程序可以修改的范围. IO classes 定义了一些functions and flags 让我们access and manipulate the condition state of a stream
  • 一旦stream 发生错误, 后续的IO 操作都失败, 比如int a; cin>>a 却输入了string. 简单方法是check the stream is okay before attempint to use, 下面的while check the state of stream returned from >> expression . 如果成功再继续
    • while 只告诉valid or not, not telling why invalid
  • iostate used to convey information about the state of a stream. This type used the collection of bits. IO 定义了四个constexprvalues of type iostate
    • 一旦badbit set, 不能再使用stream了,
    • failbit set after recoverable error, 比如读了个char when numeric expected. Possible to correct problems and continue using the stream
    • goodbit guaranteed to have value 0, 表示no failures on stream.
    • 如果任何badbit, failbit, or eofbit set, then condition that evaluates that stream fails.
    • s.good() or s.fail()(fail or bad) 是确定stream 总体状态的正确方法. 实际上, 把流当条件使用的代码等于!fail(), s.eofs.bad 检查specific error
Syntax Description
strm::iostate strm is IO的一种类型,像上面的表中一样,可以是ios::, fs::, ss::, iostate is a machine-dependent integral type that represents the condition state of a stream
strm::badbit indicate stream is corrupted(崩溃). It is not possible to use once badbit set
strm::failbit indicate IO operation failed(IO 操作失败了). failbit set after a recoverable error. 有可能fix error and continue using the stream.
strm::eofbit indicate a stream hit end-of-file (流已经到达了文件结束)
strm::goodbit indicate a stream is nt in error state. This value is guaranteed to be zero (流没有错误)
s.eof() return true if eofbit in the stream s is set (若流到达了eofbit位置)
s.fail() return true if failbit or badbit in the sream s is set
s.bad() return true if badbit is in stream s in set
s.good() return true if the stream s is in a valid state
s.clear() reset all condition values in the stream s to valid state. Return void
s.setstate(flag) reset the condition of s to flags(根据条件状态对流s置位). type of flag 需要是上面几个state 的一种. Return void
s.rdstate() return current condition of s as a strm::iostate value

check the stream before use

while (cin >> word)
// ok: read operation successful . . .

Managing the Condition State

// remember the current state of cin

auto old_state = cin.rdstate(); // remember the current state of cin 

cin.clear();// make cin valid

process_input(cin); // use cin

cin.setstate(old_state);// now reset cin to its old state


// turns off failbit and badbit but all other bits unchanged 

cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

(3). Managing the Output Buffer

  • 每一个流都管理一个缓冲区(buffer), 用来hold data that program reads and writes. 比如os << "please enter a value: ";, literal string 也许会printed immediately, or operating system 也许store the data in a buffer to print later
  • Using a buffer 可以combine serval output operations into a single system-level write. 因为writing to device 可能time-consuimg, 如果let operating system combine several output operations into a single write 可以provide an important performance boost.
  • Buffers Are not flushed if program crashes. 如果程序异常终止, 缓冲区不会刷新, 当一个程序崩溃(crash)后, 它输出的数据可能停留在输出缓冲区中等待打印
    • when debug a program that crashed, 需要make sure any output you think should have been written was actually flushed. 否则可能花大量时间track through code 为什么没有执行. 而实际上已经执行了, 只是程序crash后缓冲区没有刷新(flush), 输出的数据没有打印而已(output pending)

有几种条件导致buffer刷新(buffer flushed - write to actual output device or file )

  • The program completes normally. All output buffers are flushed as part of return from main.
  • At some indeterminate time, the buffer can become full, it will flushed before writing next value (缓冲区满了)
  • flush the buffer explicitly using a manipulator(操作符) such as endl
  • after each output operation, 可以用 manipulator unitbuf 设置流内部状态(set the stream’s internal state) to 清空缓存. 默认情况下, 对cerr是设置unitbuf的, 所以写到cerr的内容都是立即刷新的
  • Output stream might be tie to another stream. 这种情况下, when tied stream is read or written, 关联的流的缓冲区会被刷新(flush). By default,cin and cerr are both tied to cout. 因此reading to cin or writing to cerr flushes the buffer in cout.

Manipulators

  • endl: end current line and flush the buffer
  • flush flush the stream and adds no character to the output
  • ends inserts a null character into buffer and flush
cout << "hi!" << endl; // writes hi and a newline, then flushes the buffer

cout << "hi!" << flush; // writes hi, then flushes the buffer; adds no data 

cout << "hi!" << ends; // writes hi and a null, then flushes the buffer

unitbuf Manipulator: If we want to flush after every output, we can use the unitbuf manipulator. unitbuf tell the stream to do flush after every subsequent write. nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing:

cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区

 // 任何输出都立即刷新, 无缓冲

cout << nounitbuf; // returns to normal buffering

  • 当一个输入流关联到输出流, 任何试图从输入流(intput stream)读取数据的操作 都会先刷新关联的输出流(output stream).
    • The library ties cout to cin. 因此 cin >> ival 会导致cout的缓冲区刷新(flush)
  • interactive systems 通常应该关联输入流和输出流, 意味着, 用户提示的信息,都会在读操作前被打印出来

tie: has two overloaded versions.

  • one version takes no argument, 返回指向输出流指针. 如果this object is currently tie to output streams. 返回就是a pointer to the output stream。
  • 另一个version: take a pointer to an ostream, 将自己关联到ostream上, x.tie(&o): tie the stream x to the output stream o.
  • can tie either an istream or an ostream object to another ostream

下面代码中, 将一个给定的流关联到一个新的输出流, 我们将新流的指针传递给tie。而彻底解开关联的流, we pass an null pointer. 每个流最多可以关联到一个流, 但是多个流可以关联到同一个ostream

cin.tie(&cout); // illustration only: the library ties cin and cout for us 

// old_tie points to the stream (if any) currently tied to cin

ostream *old_tie = cin.tie(nullptr); // cin 不再与其他流灌流

// ties cin and cerr; not a good idea because cin should be tied to cout 

cin.tie(&cerr); // 读取cin 会刷新cerr 而不是cout 

cin.tie(old_tie); // reestablish normal tie between cin and cout

(b). File Input and Output

  • getline是从一个ifstream中读取数据. getline 不会跳过空格, 如果读取的string 第一个是空格,会保留空格
  • fstream 除了继承iostream的类型外, fstream中定义的类型还增加了一些新的成员管理与流关联的文件. 如表中, 可以对fstream, ifstream, ofstream 对象调用这些操作, 但不能对其他的IO类型调用这些操作
  • when we creat a file stream, 可以选择性提供一个file name, associate that object with file. 如果提供file name, open is called automatically.
  • 因为 fstream 除了继承iostream的类型, 可以用fstream type 代替iostream& tyes
  • 如果关联一个流从一个file到另一个file, 需要先关闭ifstream in(a); in.close() 再open in.open(name)
  • When an fstream object is destroyed, close is called automatically. 局部变量fstream离开作用域时, 关联文件会自动关闭
Syntax Description
fstream fstrm 创建一个未绑定的流. fstream是定义在fstream header中的一种类型
fstream fstrm(s) Creates an fstream and opens the file named s. s 可以是string or pointer to C-style character string. 这个constructor 是explicit的, 文件默认模式depends on the type of fstream
fstream fstrm(s,mode) 按照指定mode 打开文件
fstrm.open(s) 打开名为s的文件, 并将文件与fstrm 绑定. s 可以是string or pointer to C-style character string. 这个constructor 是explicit, 文件默认模式depends on the type of fstream
fstrm.close() 关闭与fstrm 绑定的文件,并返回void
fstrm.is_open() 返回一个bool 值,指出与fstrm 关联文件是否成功打开且尚未关闭

to verify if open succeeded is 好习惯

ifstream in(ifile); 

ofstream out; 

out.open(ifile + ".copy"); // open the specified file

if(out) // check that open succeeded 

    // the open succeeded, we can use the file

(2).File Modes

syntax Description
in Open for input
out open for output
app seek to the end before every write 每次写操作前均定位到文件末尾
ate Seek to the end immediately after the open 打开文件后立即到文件末尾
trunc Truncate the file
binary 以二进制方式进行IO operations

The mode that we can specify have following restrictions:

  • out 只能对ofstream or fstream object, out is default for ofstream
  • in 只能对 ifstream or fstream object, in is default for ifstream
  • 只有当out is specified, 才能设定trunc 模式
  • 只要当 trunc 没被设定, 就可以设定app 模式. 如果指定app, 即使没有explicitly specify out, 文件也是以输出方式打开
  • By default, truncis default mode for out(如果不设定, 默认是trunc).如果要保留文件内容, 需要specify app, or specify in mode which file is open for both input and output
  • The ate and binary modes may be specified on any file stream , 且可以与任何其他文件模式组合使用
// 下面三行是一样的

ofstream out("file1"); // out and trunc are implicit

ofstream out2("file1", ofstream::out); // trunc is implicit 

ofstream out3("file1", ofstream::out | ofstream::trunc);

// to preserve the file's contents, we must explicitly specify app mode

//下面两行是一样的

ofstream app("file2", ofstream::app); // out is implicit 

ofstream app2("file2", ofstream::out | ofstream::app);

(c). String Streams

  • sstream inherit frorm iostream header. 除了继承, sstream中定义的类型还增加了一些成员管理string, 下面表中可以对stringstream 对象调用这些操作, 但不能对其他的IO类型进行操作
  • 即使sstream and fstream share the the interface as iostream. They have no other interrelationship. We cannot use open and close on a stringstream. 也不能use str on an fstream
Syntax Description
sstream strm sstream 是定义的在sstream header 中一个类型,
sstream strm(s) strm is an sstream tat holds a copy of string s. This constructor is explicit (不能pass c-character strings)
strm.str() Returns a copy of string that strm holds
strm.str(s) copy the string s into strm. Returns void

(1). istringstream

当我们某些工作是对整行文本进行处理, 而其他一些工作是处理行内单个单词 可以用istringstream

e.g. 我们数据是如下类型,可以定义struct

morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000

下面code 中 while(record >> name)while(getline(cin, line)) 不同的是, loop reads data from string rather than the standard input. 当string completely read, “end-of-file” is signaled aand the next input operation on record will fail.

struct PersonInfo {
    string name; 
    vector<string> phones;
};

string line, word;
vector<PersonInfo> people;
while(getline(cin, line))// or while (cin >> line)

{
    PeopleInfo info;
    istringstream record(line);
    record >> info.name;
    while(record >> word){
        info.phones.push_back(word);
    }
    people.push_back(info);
}

(2). ostringstream: is useful when一点点build up a output, 但是希望最后一起output

例如继续用上面PersonInfo struct

for (const auto &entry : people) { 
    ostringstream formatted, badNums; 
    for (const auto &nums : entry.phones) {
        if (!valid(nums))
            badNums << " " << nums; 
        else
            formatted << " " << format(nums);
    }
    if (badNums.str().empty()) 
        os << entry.name << " " << formatted.str() << endl; 
    else 
        cerr << "input error: " << entry.name
            << " invalid number(s) " << badNums.str() << endl; 
}




9. Sequential Containers

sequential containers 为user 提供 control the order in which the elements are stored and accessed. Order 取决于元素加入容器(container)时的位置. By contrast, the ordered and unordered associative containers store theire elements based on the value of a key

(a). Overview

Type Description
vector Flexible-size array. Fast random access. Insert/delete elements other than at the back may be slow
deque Double-ended queue. Fast random access Fast insert/delete at front or back
list Double Linked list. Only bidirectional sequential access. Fast insert/delete at any pont in the list
forward_list Single Linked list. Only sequential access in one direction. Fast insert/delete at any point in the list (不支持reverse_iterator)
array Fixed-size array. Supports fast random access. Cannot add or remove elements
string A specialized container, similar to vector that contains characters. Fast random ccess. Fast insert/delete at the back
  • the new library containers are dramatically faster than previous release. Modern C++ programs should use library containers
  • string and vector hold their elements in contiguous memory.由下标计算其地址很快, 但是一次插入或者删除后,需要移动插入/删除位置之后的所有元素, 来保持连续存储(maintain contiguity); 而且添加一个元素可能需要分配额外的存储空间. At the case, every element must be moved into the new storage.
  • list and forward list designed to fast to add / remove element anywhere in container. 作为代价,不支持random access. 而且与string, deque ,array 相比, memory 开销(overhead) 也很大
  • 与build-in array相比, An array 是更安全容易使用的. 与build-in array一样,是fixed size的,不支持add/remove element.
  • forward_list 不支持size operation 因为storing or computing its size 多出额外的开销compared to handwritten list.

Deciding Which Sequential Container to Use

  • If your program has lots of small elements and space overhead matters, don’t use list or forward_list.
  • If the program needs to insert or delete elements:
    • Insert/Delete in the middle of the container, use a list or forward_list.
    • Insert/Delete elements at the front and the back, but not in the middle, use a deque.
    • Insert into the middle, consider using a list for the input phase. Once the input is complete, copy the list into a vector.
  • 如果程序需要random access , insertion and deletion. 决定取决于 Random Access VS Insertion/Deletion relative cost of accessing elements in a list or forward_list VS cost of inserting/ deleting in vector or deque
  • 如果不确定使用哪个, 可以在程序中只使用 operations common to both vector and list. Use iterators, not subscripts and avoid random access to elements

(b). Container Library Overview

(1). Array

  • 除了array which is fixed-size(不能add / delete), 剩下的container provide efficient, flexible memory management, growing and shrinking the size.
  • Array: size is part of its type. 当define array, 需要specify element type and size array<int, 42>.
  • 不支持normal container constructor 因为这些constructor need the size of the container.
    • 不像其他container, A default-constructed array is not empty: It has as many elements as its size(elements are default initialized)
    • 如果提供initializer,必须提供equal or less than size. 如果fewer initializers than size, remaining value是value initialized. 如果array element type is class type, class 必须有default constructor.
    • array 支持copy assignmentcopy construct, 只要type (size, element type) matches, array<int, 10>a3(a2);
    • array 支持 braced list assignment(新版C++17支持), 也支持braced list initialization array<int,10>a1; a1 = {0};
    • array 不支持 iterator constructor (Array没有no begin, end), Assign function

Array initialization

array<int, 10> ia3 = {42}; // ia3[0] is 42, remaining elements are 0

int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs; // error: no copy or assignment for built-in arrays 

array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};

array<int, 10> copy = digits; // ok: so long as array types match


array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9}; p
array<int, 10> a2 = {0}; // elements all have value 0

a1 = a2; // replaces elements in a1

a2 = {0}; // okay: assign to an array from a braced list C++17

  • The constructors that take a size are valid only for sequential containers; they are not supported for the associative containers. e.g. vector<int> a(10)
// assume noDefault is a type without a default constructor

vector<noDefault> v1(10, init); // ok: element initializer supplied 

vector<noDefault> v2(10); // error: must supply an element initializer

(2). Containner Assignment Operatior

  • Assign operations not valid for associative containers or array。 Assignment related operations invalidate iterators, references, and pointer into the left-hand container。除了string 和array 外,而swap 操作不会导致iterator, 引用,指针失效
  • Excepting array, swap does not copy, delete, or insert any elements and is guaranteed to run in constant time.
    • elements themselves are not swapped; internal data structures are swapped.
    • 但是swap arrays 会真正交换他们的element. 因此After swap, pointers, references, and iterators remain bound to the same element they denoted before the swap, . 但是值已经与另一个array中交换了(e,g,iterator 指向一样的ivec[1],只是ivec[1]的值已经换成另一个了)
  • 在library 有both mmber and nonmember version of swap,好习惯是使用nonmember version of swap
Syntax Description
c1 = c2 Replace element in c1 with copies of elements in c2, c1c2类型必须一样
c = {a,b,c} Replace elements in c1 with copies of elements in initializer list
swap(c1,c2)
c1.swap(c2)
Swap 会比copy elements 快的多
seq.assgin(b,e) Replaces elements in seq with those range denoted by iterators b and e
seq.assign(il) Replaces elements in seq with those in initializer list il
seq.assign(n,t) Replaces elements in seq with n elements with value t

Syntax common to all sequential container

Syntax Description
difference_type signed integral type big enough to thold the distance between two iterators
C c(b ,e) copy elements from range denoted by iterators b and e (not valid for array)
c.max_size() C 可保存最大的数目

Iterators

  • 对于一个reverse_iterator 进行 ++ operation, 得到上一个element
  • 对一个const object 调用iterator时, 普通的iterator 会convert to const_iterator
  • 除了array以外, every container type defines a default constructor., 且都可以接受argument that specify size and initial values
  • create a container as copy another container, 两个container类型必须匹配 C c(a). 如果pass iteratorsC c(begin, end),两个containers 类型不用相同 (只要可以convert elements to the initialized type)

对一个const object, 普通iterator 会转换成 const_iterator

list<string>::iterator it5 = a.begin();
auto it7 = a.begin(); // const_iterator only if a is const

Assign 可以用于different but compatible type

list<string> names;
vector<const char*> oldstyle;
names = oldstyle; // error: container types don't match

// ok: can convert from const char*to string 

names.assign(oldstyle.cbegin(), oldstyle.cend());


// equivalent to slist1.clear();

// followed by slist1.insert(slist1.begin(), 10, "Hiya!");

list<string> slist1(1); // one element, which is the empty string 

slist1.assign(10, "Hiya!"); // ten elements; each one is Hiya !

swap : with the exception of string, iterators, references, and pointers into the containers are not invalidated(仍然有效). 比如had iter denoted the string at sevc1[3] before swap, swap之后, iter指的是svec2[3]

vector<string> svec1(10); // vector with ten elements 

vector<string> svec2(24); // vector with 24 elements 

swap(svec1, svec2);

(3).比较container

  • Relational Operators:(>, >=, <=, <=, ==, !=), 必须保证right- and left-hand operands 必须是same kind of container and hold elements of same type (e.g. 不能拿vector<int> compare to list<int>), 比较two container 实际上是perform pariwise comparision of the elements, 与string比较方式类似
    • 两个container size 一样, element也一样,则相等, 否则不等
    • 两个container size 不一样, 但是较小的每个元素 和size 较大都一样, 则较大size 的大
    • If neither container is an initial subsequence of the other, 取决于第一个不一样的element
  • 只有当type 定义了relational operator 才可以进行比较. 比如vector<Sales_data> storeA, storeB; if (storeA < storeB); 有可能是error,如果Sales没有定义 < operator
vector<int> v1 = { 1, 3, 5, 7, 9, 12 };
vector<int> v2 = { 1, 3, 9 };
vector<int> v3 = { 1, 3, 5, 7 };
vector<int> v4 = { 1, 3, 5, 7, 9, 12 };
v1 < v2 // true; v1 and v2 differ at element [2]: v1[2] is less than v2[2]

v1 < v3 // false; all elements are equal, but v3 has fewer of them;

v1 == v4 // true; each element is equal and v1 and v4 have the same size() 

v1 == v2 // false; v2 has fewer elements than v1

(c). Sequential Container Operations

(1).Add, Access, Delete

  • 像一个vector or string 添加元素可能会引起entire object to be reallocated. Reallocating an object requires allocating new memory and moving elements from the old space to the new.
  • array不支持任何 add /delete operation 因为会改变size
  • emplace(front, back)construct elements in the container. The arguments must match a constructor for the element type.
  • The Access Members Return References (back(), front(), c[n], c.at(n)), e.g. auto & v = c.back(); v = 1024;,
    • at与下标的区别是如果越界, atthrow out_of_range error 如果invalid, (subscript operator 不会check index)

Add Element

Syntax Description
  forward_list 有自己的insert and emplace
  forward_list 不支持push_back and emplace_back
  vector, string 不支持push_front and emplace_front
c.push_back(t)
c.emplace_back(arg)
在尾部创建一个值为t或者由args 创建的元素
c.push_front(t)
c.emplace_front(arg)
 
c.insert(p,t)
c.emplace(p,args)
p is iterator, 在p之前插入,返回iterator 指向新添加元素
c.insert(p,n,t) 在p之前,, 插入n个t, 返回指向新添加的第一个元素的iterator, 若n为0, 返回p
c.insert(p,b,e) 在p之前,, 插入由iteartor b 和e返回内的元素, 返回指向新添加的第一个元素的iterator, 若range 为空, 返回p
c.insert(p,il) il是一个braced list of element values. 在p之前插入, 返回指向新添加的第一个元素的iterator, 若列表为空, 返回p

Delete Element

Syntax Description
  这些操作改变容器大小, 不适合Array
  forward_list有特殊的 erase, 不支持pop_back()
  vector, string 不支持pop_front
c.pop_back()
c.pop_front()
return void
c.erase(p) p is iterator, 删除p,返回删除元素之后的元素的 iterator,如果p is end, undefined
c.erase(b,e) b,e is iterator of range, 删除p,返回删除元素之后的元素的 iterator,如果e is end, return end iterator; if b= e,是安全的,删除一个空范围没有不良后果
c.clear return all element, returns void

理解下面insert的返回值,每次返回都是begin, 指向新的元素

list<string> 1st;
auto iter = 1st.begin(); 
while (cin >> word)
    iter = 1st.insert(iter, word); // same as calling push_front

删除元素

list<int> lst = {0,1,2,3,4,5,6,7,8,9}; 
auto it = lst.begin();
while (it != lst.end())
    if (*it % 2) // if the element is odd 

        it = lst.erase(it); // erase this element

    else
        ++it;

(3).Resize

  • 如果resize 缩小容器, then iterators, references, and pointers to the deleted elements are invalidated; resize on a vector, string, deque potentially invalidates all iterators, pointers, and references.
Syntax Description
c.resize(n) if n < c.size(), 多的元素被丢弃, 如果必须添加新的元素, value initialized
c.resize(n,t) Resize c to have n elements of t

(4). How a vector Grows

  • vectorstring 通常会分配比新的空间需求更大的内存空间,holds this storage in reserve and use it to allocate new elements as they are added. 因此而不会每次添加元素的是都reallocate
  • vector implementation strategy 是 doubling the current capacity each time it has to allocate new storage.(1->2->4->8->16…)
  • capacity 告诉我们how many elements the container can hold before it must allocate more space. size is the number of a elements the container already holds
  • reserve 允许我们通知container how many elements it should prepare to hold. reserve 不改变容器中元素的数量,仅影响预先分配多大的内存空间 (how much memory the vector preallocates )
    • 如果request space > current capacity, reserve allocates at least as much as requested amount
    • 如果request size 小于 或者 等于 existing capacity, reserve does nothing.
    • reserve never reduce the amount of space that container uses.. 如果想减少memory, 可以call shrink_to_fit Calling shrink_to_fit is only a request; there is no guarantee that the library will return the memory.比如当size == capacity
    • 对于 deque, vector, or srtring; resize 只改变容器中元素数目, 而不改变its capacity
Syntax Description
  shrink_to_fit valid only for vector, string, deque
  capcity() and reserve valid only for vector, string
c.shrink_to_fit(n) Request to reduce capacity() to equal to size()
c.capacity(n,t) 不重新分配的话,c可以保存多少元素
c.reserve(n) Allocate space for at least n elements

用光预留空间

vector<int> ivec;
while (ivec.size() != ivec.capacity())
    ivec.push_back(0);

(d). Invalidate Iterators

  • Invalidate iterator, pointer, or reference is serious run-time error
  • After Options that add elements: (Iterators, pointers, and references aka IPR)
    • IPR to vector or strings are invalid 如果 container reallocated . 如果没有reallocate, 插入元素之前的IPR有效, 插入元素之后的IPR 无效
    • 对于 deque, add elements in the middle, IPR都invalid; 在 front or back, Iterators are invalidated, but references and pointers to existing elements not affected
    • Adding (insert) elements to vector, string, deque 会invalidates all existing IPR
    • 对于 list, forward_list IPR都 都有效
  • After we remove element
    • list, forward_list IPR 都 remain valid
    • 对于 deque, remove elements in the middle IPR都invalid; 如果删除是back or front, begin/off-the-end itertor(end) is invaldiated, front/back的reference, pointer有效, 其他middle的IPR 都有效 (unaffected)
    • IPR to vector or string remain valid对于删除元素之前的, 注意: 当我们删除元素时, off-the-end iterator is always invalidated when we remove elements
  • Avoid Storing the Iterator Returned from end 因为add / remove elements 总会把vector or stringend iterator invalid
// silly loop to remove even-valued elements and insert a duplicate of odd-valued elements

vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto iter = vi.begin(); // call begin, not cbegin because we're changing vi

while (iter != vi.end()) { 
    if(*itera%2)
        iter = vi.insert(iter, *iter); // duplicate the current element

        iter += 2; // advance past this element and the one inserted before it

    } else
        iter = vi.erase(iter); // remove even elements

        // don't advance the iterator; iter denotes the element after the one we erased

}

Avoid Storing the Iterator Returned from end: 不要特地的保存end, 下面的代码行为是未定义的, 会导致代码无限循环

// disaster: the behavior of this loop is undefined 

auto begin = v.begin(),
    end = v.end(); // bad idea, saving the value of the end iterator 

while (begin != end) {
    // insert the new value and reassign begin, which otherwise would be invalid 

    ++begin; // 想在此元素后插入元素

    begin = v.insert(begin, 42); // insert the new value 
    
    ++begin; // advance begin past the element we just added

}


//正确做法

while (begin != v.end()) {
    ++begin; // advance begin because we want to insert after this element 
    
    begin = v.insert(begin, 42); // insert the new value
    
     ++begin; // advance begin past the element we just added

}

(e).Forward_list Operations

  • forward_list`
    • 没有back,因为不支持reverse_iterator,
    • 也不能--递减forward_list的iterator, 但可以call forward_listend()
    • 不支持push_back
    • 有自己的insert and emplace

因为forward_list 是single linked list, 如果add/remove element 需要访问前一个元素(prepocessor),但是forward_list是单向,不能访问前一个元素, 所以定义insert emplace, erase 都是after, e.g. 我们想删除elem3 需要call erase_after on iterator elem2. To support 这种操作, forward_list 也定义了一个before_begin returns off-the-begining (首前) iterator

Syntax Description
c.before_begin()
c.cbefore_begin()
不存在的元素在列表首前, 不能用dereferenced
c.insert_after(p,t)
c.insert_after(p,n,t)
c.insert_after(p,b,e)
c.insert_after(p,il)
p is iterator, t is object, n is count, b 和 e是iterator, il is braced list. 返回last insert element 如果range is empty returns p, Undefined if p 是off-the-end iterator (end)
c.emplace_after(p, args) 返回the iterator to new element, Undefined if p 是off-the-end iterator (end)
c.erase_after(p)
c.erase_after(b,e)
删除p后面的一个elements or 删除(b,e)之间的元素 (不包括b,e). 返回an iterator to the element after the one deleted, or 返回off-the-end iterator(如果不存在这样的element), Undefined if p 是off-the-end iterator (end)

当删除elements in forward_list 必须关注两个iterator, the one we checking and the one to that element’s predecessor.

forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin(); // denotes element "off the start" of flst

auto curr = flst.begin();
while (curr != flst.end()) { // while there are still elements to process

    if (*curr % 2)
        curr = flst.erase_after(prev); // erase it and move curr

    else {
        prev = curr;
        ++curr;
    }
}

(f).String Operations

(1). 其他构造string 的方法

Syntax Description
  n, len2, pos2 are all unsigned values
string s(cp, n) s is a copy of the first n character in array which cp points. Array 必须有至少n个char
string s(s2, pos2) s is a copy of characters in the string s2 starting from the index pos2. Undefined if pos2>s2.size()
string s(s2, pos2, len2) s is a copy of characters in string s2 [pos2, pos2+len2 ), 不管len2多大, 最多copy s2.size() - pos2个chars
  • 当create a string from a const char*, array 必须是 null terminated., copy 遇到null 停止. or pass a count , array 可以不用以null 结尾
    • 但是如果不pass count and no null terminated, 或者given count 大于size of array, operation is undefined
  • 当pass的起始值pos2 大于size, 会throw out_of_range exception.
  • library copies up to the size of string or null 结尾的char array
const char *cp = "Hello World!!!"; // null-terminated array

char noNull[] = {'H', 'i'}; // not null terminated

string s1(cp); // copy up to the null in cp; s1 == "Hello World!!!"

string s2(noNull,2); // copy two characters from no_null; s2 == "Hi" 

string s3(noNull); // undefined: noNull not null terminated

string s4(cp + 6, 5);// copy 5 characters starting at cp[6]; s4 == "World" 

string s5(s1, 6, 5); // copy 5 characters starting at s1[6]; s5 == "World" 

string s6(s1, 6); // copy from s1 [6] to end of s1; s6 == "World!!!" 

string s7(s1,6,20); // ok, copies only to end of s1; s7 == "World!!!" 

string s8(s1, 16); // throws an out_of_range exception

string operation

Syntax Description
s.substr(pos, n) pos起始点, n 是从起始点copy 多少个, [pos, pos+n), 会throws an out_of_range exception 如果pos超过size
  定义了member function assign, erase, insert
s.append(args)  
s.assign(args) replace chararcters in s = args
s.erase(pos,len) 删除[pos, pos+len), 如果没有提供len, 表示 删除[pos, end), return a reference to s
s.insert(pos,args) pos 可以是下标 or iterator, 在p之前插入args, 接受index的版本 返回reference to s; 接受iterator的返回an iterator 表示第一插入的character
s.replace(range,args) 删除range 内的字符, 替换成args指定的字符, range 可以是一个下标或者一个长度, or a pair of iterators into s. returns a reference to s

上表的arg 可以是如下的形式

Syntax Description
str string str
str, pos, len Up to len characters from str starting at pos
cp, len Up to len characters from the character array pointed by cp
cp Null-terminated array pointed to by pointer cp
n,c n copies of char c
b,e Characters in range formed by iterators b and e
initializer list Comma-separated list of characters enclosed in braces

(2). String Search Operations

  • 有6中search的functions, 每个function 提供4个overloaded functins. 每一个search 返回 string::size_type value that is the index of where match occurred 如果没有match, function 返回一个 static member named string::npos. Library defines npos as a const string::size_type 初始值 -1. 因为npos是unsinged type. 意味着npos等于任何string最大可能的值得大小

string river("Mississippi");
auto first_pos = river.find("is"); // returns 1 

auto last_pos = river.rfind("is"); // returns 4

(3). Compare Functions

compare function 类似于 C library的 strcmp function. Like strcmp, s.compare 返回0(等于), positive(大于) or negative(小于) value

Syntax Description
s2 Compare s to s2
pos1, n1, s2 将s中 [pos1, pos1 + n1) compare to s2
pos1, n1, s2, pos2, n2 将s中 [pos1, pos1 + n1 ) compare to s2 的[pos2, pos2 + n2 )
cp compares s to null-terminated array pointed to by cp
pos1, n1, cp 将s中 [pos1, pos1 + n1) compare to cp
pos1, n1, cp, n2 将s中 [pos1, pos1 + n1) compare to n2 characters starting from pointer cp

(4).Numeric Conversions

  • 如果string 不能转换为一个数值, 转换 functions 会 throw an invalid_argument exception. 如果转换得到数值无法用任何类型来表示, throw out_of_range
Syntax Description
stoi(s, p, b)
stol(s, p, b)
stoul(s, p, b)
stoll(s, p, b)
stoull(s, p, b)
b表示numeric base for the conversion(几进制), p is a pointer to a size_t in which to put index of first non-numeric character(非数值的) in s`
stof(s,p)
stod(s,p)
stold(s,p)
p跟上面的作用一样

(g). Container Adaptors

  • 除了 sequential containers, 还定义了sequential container adaptors: stack, queue, and priority_queue. An adaptor 是 general concept.
  • container adaptor takes an existing container type and makes it act like a different type.
  • Each adaptor defines two constructors: the default constructor that creates an empty object, and a constructor that takes a container and initializes the adaptor by copying the given container.
  • 默认情况下, stackand queue are implemented in terms of deque, 而priority_queue是在vector上实现的
    • e.g. 假定deq是一个deque<int>, stack<int>stk(deq); 从deq拷贝元素到stk,
    • e.g. override default container type by naming a sequential container as a second type argument. stack<string, vector<string>>str(svec)
  • All adpator 需要the ability to add / remove elements. 因为they cannot be built on array. 同样的, 我们也不能用forward_list, 因为adaptor需要具有add, remove, access the last element (back) ability.
  • stack仅需要有back, push_back, pop_back,所以用于除了arrayforward_list以外所有容器 (list. deque,vector,string)
  • queue需要有back,push_back, front, push_front的能力,所以只能建立在list or deque上而不能在vector上.
  • priority_queue 需要有random access in addition to front, pusk_back, pop_back, 所以只能用于vector or deque, 不能用于list
  • 每一个container adpator defines 自己的操作类型 in terms of 底层容器的操作类型. 只能使用adaptor自己的操作类型,不能用underlying container type的操作类型.
    • intStack.push(ix); calls push_back on the deque on which intStack is based,尽管stack是基于deque实现的, 但我们不能使用deque操作, 不能call push_back, 只能call push




10. Generic Algorithms

(a). Overview

Kep Concept: Algorithms Never Execute Container Operations. They operate solely in terms of iterators and iterator operations. 有个重要的implication: Algorithm never change the size of the underlying container, Algorithm 可能改变container中的值, 可能会move elements within the container。 但是never add or remove elements directly. A special class of iterator is inserter 会插入elements, 但是算法自身itself never does so

(b), First Look at the Algorithm

  • Read-Only Algorithms
    • never write to element
    • it is best to use cbegin() and cend() with algorithms that read, but do not write
    • Algorithm that take a single iterator denoting a second squence, 都假定 second sequence is at least as large at the first(至少一样长)
      • 比如equal中的第二个sequence 没有第一个长, 程序会报错
    • 一些算法从两个序列(containers)中读取元素, 不一定要求两个container 是一样类型, 也不要求element types are indentical. e.g. 见表中的equal
  • Algorithms That Write Container Elements
    • Some algorithms assign new values to the elements in sequence, 必须确保序列原大小 必须大于 我们要求写入的 元素数目。算法不执行container operations, no way to change the size of a container
    • Algorithms Do Not Check Write Operations: Algorithms that write to a destination iterator assume the destination is large enough to hold the number of elements being written. 会假定container 有足够的size (容量)来操作 比如fill_n, 如果空间不够, undefined behavior
    • 还有一些算法 so called “copying” version, 这些算法compute new element values, but instead of putting them back into input sequnce, 算法创建一个新序列保存这些结果, 例如replace_copy
  • 一种确保algorithm has enough elements to hold is to use insert iterator (right-hand value is added to container)
    • back_inserter: takes a reference to a container and returns an insert iterator bound to that container. 当assign through that iterator, assignment calls push_back to add an element with the given value to the container

Read-only Algorithm

Syntax Description
accumulate accumulate第三个类型决定了使用哪个加法运算符和返回类型(注意:不可以传入empty string as string literal)
string sum = accumulate(v.cbegin(), v.cend(), string(""));
equal 判断两个sequences hold the same value, compare each elements, return true 如果一样, 否则false; equal 不一定需要两个cointainer 一样 类型也一样, 可以是不同的container, 不同的类型, 只需可以用==来比较来自两个序列的元素
//accumulate

int sum = accumulate(vec.cbegin(), vec.cend(), 0);
string sum = accumulate(v.cbegin(), v.cend(), string(""))
// error: no + on const char*

string sum = accumulate(v.cbegin(), v.cend(), "");

//equal

// roster2 should have at least as many elements as roster1 

equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());

list<int> a = { 1,2,3,4,5 };
array<int,5> b = { 1,2,3,4,5 }; 
cout << equal(a.begin(), a.end(), b.begin());//print 1

accumulate 可以用于 self-defined class, 只要class overload addition operator, pass 到 accumulate 需要pass as functor(rvalue) with constructor

class myobj {
public:
     int a = 0;
     myobj(string s) {}
     myobj& operator+(const myobj& l) {
          this->a += l.a;
	  return (*this);
     }
};

myobj a("1"); myobj b("2");
a.a = 1; b.a = 2;
vector<myobj> vec = { a,b };

myobj c = accumulate(vec.begin(), vec.end(), myobj("whatever string"));
cout << "c value " << c.a << endl; //print 3

Algorithm that write element

Syntax Description
fill 将给定的值赋予序列中每个元素, 有点像resize, assign,但不会像它们一样改变size
fill_n fill_n(dest, n, val) takes a iterator, a count and a value. 从iterator声明的位置开始, fill n 个 元素 value. assumes that dest refers to an element and that there are at least n elements in the sequence starting from dest.
copy 接受三个iterator, 前两个表示input range, 第三个表示beginning of the destination sequence: copy from input range to destinaion. 很重要的是: destination passed to copy 至少跟input range 一样长, The value returned by copy is the (incremented) value of its destination iterator.(就是完成copy后的下一点)
replace 接受4个参数, 前两个是iterator, 表示输入序列, 后两个一个是要搜索的值, 一个是replace的值
replace_copy replace想法一样,只不过不更改input sequence, 接受5个参数, 前两个是iterator, 表示输入序列, 第三个表示write的destination, 后两个一个是要搜索的值, 一个是replace的值
//fill

fill(vec.begin(), vec.begin() + vec.size()/2, 10);

//fill_n: Do Not Check Write Operations

vector<int> vec; // empty vector

// disaster: attempts to write to ten (nonexistent) elements in vec 

fill_n(vec.begin(), 10, 0);//The result is undefined.


//copy

int a1[] = {0,1,2,3,4,5,6,7,8,9};
int a2[sizeof(a1)/sizeof(*a1)]; // a2 has the same size as a1

// ret points just past the last element copied into a2

auto ret = copy(begin(a1), end(a1), a2); // copy a1 into a2

// ret will point just past the last element copied into a2.


//replace 

replace(ilst.begin(), ilst.end(), 0, 42);//把所有的0 替换成42

// use back_inserter to grow destination as needed 

replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);

//把范围内所有的0替换成42, 并push_back到ivec

Algorithms That Reorder Container Elements

Syntax Description
sort  
stable_sort sort 不同的是, stable_sort会维持相等元素原有的序列
unique 消除相邻的重复(如果不相邻一样的,不会删除), 因为算法不对容器进行操作, 不能直接添加/删除元素, 返回an iterator that denotes the end of the range of the unique value
vector<int>  = {10,20,20,20,30,30,20,20,10};   // 10 20 20 20 30 30 20 20 10

  // using default comparison:

std::vector<int>::iterator end_unique = std::unique (myvector.begin(), myvector.end());   

// 10 20 30 20 10 ?  ?  ?  ?

                  ^
                  |
                end_unique                              

(c), Lambda / Bind

  • A predicate is an expression that can be called and that returns a value that can be used as a condition. Has two version:
    • unary predicate: they have a single parameter
    • binary predicates: they have two parameters, 比如sort
  • 算法call the given predicate on the elements in the input range. 因此, must be possible to convert the element type to the parameter type of the predicate

(1). Lambdas:

  • 可以pass any kind of callable object to an algorithm. A object or expression is callable if we can apply the call operator to it. e(args).
  • lambda expression 可以想成是inline function, [capture list] (parameter list) -> return type { function body }
    • capture list is 局部变量(所在函数内部,lambda外部)列表 defined in the enclosing function(通常为空). A lambda may use a variable local to its surrounding function only if the lambda captures that variable in its capture list.
      • The capture list is used for local nonstatic variables only; lambdas can use local statics and variables declared outside the function directly(比如cout)
    • return type:
      • 如果specify return type的话, unlike ordinary functions, a lambda must use a trailing return (尾置返回) to specify its return type.
      • 如果忽略返回类型, lambda 根据函数体的代码推断出返回类型.
    • can omit either or both of the parameter list and return type but must always include the capture list and function body auto f = [] { return 42; }; 定义了f callable object that takes no arguments and returns 42.cout << f() << endl; // prints 42
    • parameter:
      • lambda中忽略parameter list 等于指定一个empty parameter list.
      • lambda 不能有默认参数. 因此call lambda 的参数永远与lambda 的参数一样多
    • When we define a lambda, the compiler generates a new (unnamed) class type that corresponds to that lambda and an unnamed object of that type. 使用auto 定义一个lambda 变量的初始值时, define an object of the type generated from that lambda.
      • 类似普通data members of any class, the data members of a lambda are initialized when a lambda object is created.
    • can also return a lambda from a function. The function might directly return a callable object or the function might return an object of a class that has a callable object as a data member(functor). 如果Function returns a lambda, 不能return a reference to a local varaibles (不能有reference captures)

Capture:

  • Capture by value auto f = [v1] { return v1; };. 前提是变量必须是可以copy的,Unlike parameters, 被捕获的值是在lambda 创建时copy 而不是在被调用(call)时copy,因此随后的对其修改不会影响到lambda 内对应的值
    • If we want change the value of a captured variable, we must follow the parameter list with the keyword mutable.
      • lambda that are mutable不能省略parameter list auto f = [v1] () mutable { return ++v1; };, 不加mutable不能更改
  • Capture by Reference: auto f2 = [&v1] { return v1; };when use the variable inside the lambda body, 使用的是引用所绑定的对象, 比如返回v1, 返回的是v1 指向对象的值
    • 必须确保 the referenced object exists at the time the lambda is executed(被引用对象在lambda 执行时候是存在的).因为捕获的都是局部变量. 这些变量在函数结束后就不存在了,如果lambda 可能在函数结束后执行, 捕获引用指向的局部变量已经消失
    • 捕获引用是有必要的, 比如函数接受一个ostream的引用,
    • 一个variable captured by reference 是否可以改变 depends only on whether that reference refers to a const or nonconst type (如果variable 指向const object不能修改, 如果不是const 可以修改); - const int a = 0; auto j = [&a] {cout << a << endl; }; 不能在lambda 中 修改 a 的值
  • Implicit Captures: 可以让compiler根据lambda中代码infer which variables we use from the code. To direct the compiler to infer the capture list, we use an &(capture by reference) or =(capture by value) in the capture list.
  • Mix implicit and explicit Capture:
    • 必须确保the first item in capture list is &or =
    • Explicit capture variables must use the alternate form(显示捕获和隐式捕获必须是不同的方式): 如果隐式捕获是引用(&), 则显示捕获必须是值, 不能在显示名字前加上&; 如果隐式捕获是值方式, 则显示捕获必须用引用方式 (加上&)

Implicit Capture

// sz implicitly captured by value

wc = find_if(words.begin(), words.end(),    
    [=](const string &s) { return s.size() >= sz; });

we can mix implicit and explicit captures:

void biggies(vector<string> &words, vector<string>::size_type sz,
        ostream &os = cout, char c = ' ')
{
// os implicitly captured by reference; c explicitly captured by value

for_each(words.begin(), words.end(),
        [&, c](const string &s) { os << s << c; });

// os explicitly captured by reference; c implicitly captured by value 

for_each(words.begin(), words.end(),
        [=, &os](const string &s) { os << s << c; });
}

Mutable Lambdas: 也显示了capture by value时, 如果 值lambda 建立后, 在call function 前改变了, 不影响lambda function 内值

void fcn3()
{
    size_t v1 = 42; // local variable

    // f can change the value of the variables it captures

    auto f = [v1] () mutable { return ++v1; }; 
    v1 = 0;
    auto j =f();// j is 43

}

void fnc4()
{
    size_t v1 = 42; // local variable

    // v1 is a reference to a non const variable

    // we can change that variable through the reference inside f2

    auto f2 = [&v1] { return ++v1; }; 
    v1 = 0;
    autoj=f2();// j is 1

}

Capture Advise: 需要keep your lambda captures simple

  • A lambda capture stores information between the time the lambda is created (i.e., when the code that defines the lambda is executed) and the time (or times) the lambda itself is executed (捕获保存信息从定义到执行).
  • Capture ordinary variable 比如int, string, or nonpointer type - by value is usually straightforward. 这种情况下, 只需关注在捕获时有我们需要的值就可以了
  • 如果捕获pointer, iterator, or capture by reference, 必须ensure pointer, iterator, or reference still exists whenever the lambda executes. 还可能是, 在指针或引用被捕获时候, 绑定对象的值是我们期望的, 但在lambda执行时, 该对象的值可能完全不同了
  • if possible, avoid capturing pointers or references.
void biggies(vector<string> &words, vector<string>::size_type sz,
ostream &os = cout, char c = ' ')
{
// statement to print count revised to print to os 

for_each(words.begin(), words.end(),
    [&os, c](const string &s) { os << s << c; });
}

Specifying the Lambda Return Type

  • 默认情况下, 一个lambda body contains any statement 除了return, that lambda is assumed to return void. lambdas inferred to return void may not return a value.
  • When we need to define a return type for a lambda, we must use a trailing return type
//error: cannot deduce the return type from the lambda(新版C++ 是可以的)

transform(vi.begin(), vi.end(), vi.begin(),
    [](int i) { if (i < 0) return -i; else return i; 
});

transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int
    { if (i < 0) return -i; else return i; });

(2).Binding Arguments:

  • Lambda expression are most useful for 只在一两个地方使用的简单操作, 如果在很多地方使用相同操作, 通常定义一个函数
  • 比如find_if函数,如果不用捕获, 无法让只接受一个argument的predicate 接受另一个size_type的variable, 可以用bind(in functional heade)来解决这个问题,It takes a callable object and generates a new callable that “adapts” the parameter list of the original object.
  • auto newCallable = bind(callable, arg_list); : the general form of a call to bind
    • newCallable is itself a callable object
    • arg_list is 逗号分隔的参数列表,对应给callable的parameter
      • 也许include names of the form _n, n是整数, 这些参数是”placehorders”(in functional header),表示newCallable parameters. They stand “in place of” the arguments that will be passed to newCallable: _1newCallable第一个参数,_2newCallable第二个参数
      • The _n names are defined in a namespace named placeholders(in std), using declarations, for _1 is using std::placeholders::_1;, 表示我们要使用命名_1`
      • Must provide a separate using declaration for each placeholder name that we use. 这么写容易出错error-prone, 我们可以用using namespace std::placeholders;表示all the names from namespace accessible to our program
    • 当调用newCallable, newCallable会调用callable, 并传递给它arg_list中的参数
  • can use bind to bind or rearrange the parameters in the given callable
  • Binding Reference Parameters 有时we have arguments that we want ot bind by reference or we want to bind an argument that has a type that we cannot copy, 用ref:
    • ref returns an object that contains the given reference and that is itself copyable.标准库中还有另一个cref: generates a class that holds a reference to const, 生成一个保存const的引用类
      • Modern C++ programs should use bind. 旧版C++提供了bind1stbind2nd有更多限制,已经被deprecated 在新版中

下面的function bind has only one placeholder, which means that check6 takes a single argument。The placeholder appears first in arg_list 对应 check_size的第一个参数 (const string&),表示调用check6必须一个string 参数

bool check_size(const string &s, string::size_type sz)
{ return s.size() >= sz; }

// check6 is a callable object(可调用对象) that takes one argument of type string

 // and calls check_size on its given string and the value 6
 
auto check6 = bind(check_size, _1, 6);

string s = "hello";
bool b1 = check6(s); // check6(s) calls check_size(s, 6)


//使用bind可以改变原来的lambda的find_if

auto wc = find_if(words.begin(), words.end(), [sz](const string &a) 

auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));

can use bind to bind or rearrange the parameters in the given callable. 比如assume f is a callable object that has 5个参数, gis the callable that takes 2 arguments, 传递给g的参数按顺序绑定到placeholder, 第一个参数绑定到_1, 第二个参数绑定到_2, 第一个参数将被传递给f的最后一个参数, 第二个参数将被传递给f的第三个参数

// g is a callable object that takes two arguments 

auto g = bind(f, a, b, _2, c, _1);

//calling g(X, Y) calls

f(a, b, Y, c, X)

Using to bind to Reorder Parameters: use bind to invert the meaning of isShorter by writing

// sort on word length, shortest to longest

sort(words.begin(), words.end(), isShorter);
// sort on word length, longest to shortest

sort(words.begin(), words.end(), bind(isShorter, _2, _1));

Binding Reference Parameters: 比如ostream , 因为IO 对象不能拷贝,

// os is a local variable referring to an output stream 

// c is a local variable of type char 

for_each(words.begin(), words.end(),
    [&os, c](const string &s) { os << s << c; });

ostream &print(ostream &os, const string &s, char c)
{
    return os << s << c;
}

// error: cannot copy os

for_each(words.begin(), words.end(), bind(print, os, _1, ' '));

for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));

(d). Iterators

  1. Insert iterator: 这些迭代器被绑定到一个容器上, 可以像容器插入元素
  2. Stream iterator: 这些迭代器被绑定到输入或者输出流上, 可以用来iterate through the associated IO stream
  3. Reverse iterator: 这些迭代器向后(backward)而不是向前移动. Container 除了 forward_list 都有reverse iterators
  4. Move iterator: 不是拷贝其中的元素而是移动它们

(1). Insert Iterator

An inserter is an iterator adaptor that takes a container and yields an iterator that adds elements to the specified container. 当赋值给insert iteartor, the iterator calls a container operation to add an element at a specified positon in the given container.

Syntax Description
it=t it指定的当前位置插入t, 假定cit绑定的容器, 依赖于插入迭代器的不同种类, 此赋值会分别调用 c.push_back(t), c.push_front(t), or c.insert(t,p), 其中p为传递给inserter的iterator position
*it, ++it, it++ 此操作虽然存在, 但不会对it做任何事情. 每个操作都返回it

有三种Insert inserter: we can use front_inserter only if container has push_front. 同样 use back_inserter only if it has push_back

  • back_inserter: creates an iterator use push_back
  • front_inserter: creates an iterator use push_front
  • inserter: creates an iterator uses insert. 此函数接受第二个参数, 这个参数必须是指向给定容器的iterator, 元素将被插入到给定iterator的前面连续插入elements, inserter(c,iter), 会插入到iter所指向元素的前面, c 是container
    • 如果it is an iterator generated by inserter, then an assignment as *it = val; 等于 it = c.insert(it,val); ++it;
list<int> lst = {1,2,3,4};
list<int> lst2, lst3; // empty lists


 // after copy completes, 1st2 contains 4 3 2 1

copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
// after copy completes, 1st3 contains 1 2 3 4

copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));

//or 

insert_iterator<list<int>>insert_it(lst3, lst3.begin());
copy(lst.begin(), lst.end(), insert_it);
list<int> l;
auto it = inserter(l, l.begin());
it = 1; it = 2;

copy(l.begin(), l.end(), ostream_iterator<int>(cout, ","));//print 1, 2,
	

back_inserter; apply to fill_n: 因为we passed an iterator returned by back_inserter(back_iterator返回的迭代器), each assignment will call push_back on vec 一点主意下面例子的copy 用于 ostream_iterator 而for_each 不用于 ostream_iterator 因为for_each最后一个argument 是 fn, 要pass to argument to _Func(*itr);, 而copy 最后一个argument 是 iterator, *result = *first;

vector<int> vec; // empty vector

auto it = back_inserter(vec); // assigning through it adds elements to vec

it = 342; // vec now has one element with value 42, size = 1

*it = 42; // vec now has one element with value 42, size = 2

//因为*it = 会call push_back, 向后推, it iterator 不会前进,


//用到fill_n 

vector<int> vec; // empty vector

// ok: back_inserter creates an insert iterator that adds elements to vec 

fill_n(back_inserter(vec), 10, 0); // appends ten elements to vec

back_insert class, C++14, addressof C++14 新feature

template <class Container> class back_insert_iterator :
	public iterator<output_iterator_tag,void,void,void,void>
{
protected:
  Container* container;
public:
   explicit back_insert_iterator (Container& x) : container(std::addressof(x)) {}
  back_insert_iterator<Container>& operator= (const typename Container::value_type& value)
    { container->push_back(value); return *this; }
  back_insert_iterator<Container>& operator= (typename Container::value_type&& value)
    { container->push_back(std::move(value)); return *this; }
  back_insert_iterator<Container>& operator* ()
    { return *this; }
  back_insert_iterator<Container>& operator++ ()
    { return *this; }
  back_insert_iterator<Container> operator++ (int)
    { return *this; }
};

insert_iterator:

template <class Container>
  class insert_iterator :
    public iterator<output_iterator_tag,void,void,void,void>
{
protected:
  Container* container;
  typename Container::iterator iter;

public:
  typedef Container container_type;
  explicit insert_iterator (Container& x, typename Container::iterator i)
    : container(std::addressof(x)), iter(i) {}
  insert_iterator<Container>& operator= (const typename Container::value_type& value)
    { iter=container->insert(iter,value); ++iter; return *this; }
  insert_iterator<Container>& operator= (typename Container::value_type&& value)
    { iter=container->insert(iter,std::move(value)); ++iter; return *this; }
  insert_iterator<Container>& operator* ()
    { return *this; }
  insert_iterator<Container>& operator++ ()
    { return *this; }
  insert_iterator<Container> operator++ (int)
    { return *this; }
};

(2). iostream Iterator

  • istream_iterator: reads an input stream, ostream_iterator: writes an output stream
  • with stream iterator, we can use the generic algorithms to read data from or write data to stream objects.
  • need to specify type of objects that the iterator will read or write. like istream_iterator<int> int_it(cin);
  • istream_iterator use >> to read a stream.
    • the type that an istream_iterator reads must have an input operator defined. (比如int, string 都有input operator defined)
    • 当我们create an istream_iterator, we can bind it to a stream (绑定到流上).
    • 还可以使用默认initialize iterator constructor, which creates an iterator that we can use as the off-the-end value (可以当尾后使用的迭代器)
    • An iterator bound to a stream is equal to the end iterator(用默认iostream_iteratorconstructor 生成的iterator) once its associated stream hits end-of-file or encounters an IO error.
    • istream_iterators Are Permitted to Use Lazy Evaluation: 当绑定流到 istream_iterator, 不保证it will 立即 read the stream. The implementation is permitted to delay reading the stream until we use the iterator. 我们保证的是: 在一次dereference the iterator前, the stream will have been read.
      • 对于大多程序, 何时读取没有差别, 但是如果我们创建一个istream_iterator, 没有读取就销毁了, 或者我们从两个不同的对象同步读取the same stream,那么何时读取就很重要了
  • ostream_iterator: 可以对任何具有输出运算符的(<<)的对象定义ostream_iterator,当创建ostream_iterator, 可以提供一个a character string as second argument, 在输出每个元素后都会打印这个字符串, 字符串必须是C-style character string (string literal or a pointer to a null terminated array)
    • 必须将ostream_iterator 绑定到一个指定的流上
    • There is no empty or off-the-end ostream_iterator
  • 实际上*++ operator 对ostream_iterator没有任何影响
Syntax Description
istream_iterator<T>in(is) in从输入流is读取类型为T的值
istream_iterator<T>end 读取类型为T的值的istream_iterator iterator, 表示尾后位置
in1 == in2
in1 != in2
in1in2必须读取相同类型. 如果他们都是尾后iterator, 或绑定到相同的input stream则他们相等
*in 返回从流中读取的值
in->mem (*in).mem 含义相同
++in
in++
Reads the next value from the input stream using the >> operator for the element type. 通常上prefix version returns a reference to the incremented iterator. The postfix version returns the old value
Syntax Description
ostream_iterator<T>out(os) out将类型为T的值写到输出流os
ostream_iterator<T>out(os, d) out将类型为T的值写到输出流os中, 每个值后面都输出一个d, d points to a null-terminated character array
out = val << 运算符将val 写入到 out 绑定的 ostream 中,val的类型必须与 out 可写的类型 兼容
*out
++out
out++
这些运算符是存在的, 但不对out做任何事情, 每个运算符都返回out

下面第二个例子是从cin 读取int值, 保存到vec中, 每个循环检查in_iter是否等于eof, eof被定义为空的istream_iterator which is used as the end iterator

istream_iterator<int> int_it(cin); // reads ints from cin end iterator value

istream_iterator<int> int_eof; // end iterator value

ifstream in("afile"); 
istream_iterator<string> str_it(in); //reads strings from "afile"


//从标准输入读取数据

istream_iterator<int> in_iter(cin); //read ints from cin 

istream_iterator<int> eof; //istream 尾后iterator

while(in_iter!=eof) // while there's valid input to read

// postfix increment reads the stream and returns the old value of the iterator 

// we dereference that iterator to get the previous value read from the stream 

    vec.push_back(*in_iter++);

可以将上面程序重写,construct vec from a pair of iterators that denote a range of elements. This constructor reads cin until it hits end-of-file or encounters an input that is not an int.

istream_iterator<int>in_iter(cin), eof; //read ints from cin 

vector<int> vec(in_iter, eof); //construct vec from an iterator range

Using Stream Iterators with the Algorithms

istream_iterator<int> in(cin), eof; 
cout << accumulate(in, eof, 0) << endl;

ostream_iterator: 下面程序将每个元素写到cout, 每个元素后面加一个空格. Each time we assign a value to out_iter, the write is committed. 推荐下面第一种写法,That loop uses the iterator consistently with how we use other iterator types.

ostream_iterator<int> out_iter(cout, " "); 
for (auto e : vec)
    *out_iter++ = e; // the assignment writes this element to 

cout cout << endl;

//方法二: 实际上可以忽略解引用 和 递增运算,和上面作用一样的

for(auto e: vec)
    out_iter = e;//赋值语句将元素写到out

cout<<endl;

//方法三:

copy(vec.begin(),vec.end(), out_iter);
cout << endl;

work with class

istream_iterator<Sales_item> item_iter(cin), eof; 
ostream_iterator<Sales_item> out_iter(cout, "\n"); 
// store the first transaction in sum and read the next record Sales_item 

sum = *item_iter++;
while (item_iter != eof) {
    // if the current transaction (which is stored in item_iter) has the same ISBN 
    
    if (item_iter->isbn() == sum.isbn())
        sum += *item_iter++; // add it to sum and read the next transaction

    else {
        out_iter = sum; // write the current sum

        sum = *item_iter++; // read the next transaction 

    }
} 
out_iter = sum; //打印最后一组记录的和

(3). Reverse Iterators

  • A reverse iterator is an iterator that traverses a container backward, from the last element toward the first.
  • ++it: move the iterator to the previous element, --it moves the iterator to the next element.
  • 除了forward_list 所有container 都支持 reverse iterators
  • obtain a reverse iterator by rbegin, rend, crbegin, and crend: to last element and one “past” the beginning of the container
  • 下面例子只中[line.crbegin(), rcomma)[rcomma.base(), line.cend()) 指向相同的元素范围, 所以rcommarcomma.base() 必须生成相邻而不是相同的位置. crbegin()cend()也是如此
    • When we initialize or assign a reverse iterator from a plain iterator, the resulting iterator does not refer to the same element as the original.

找到从结尾到最后一个逗号之间的词, 但注意reverse_iterator会反向打印line的内容, 需要用base 完成这一个转换

auto comma = find(line.cbegin(), line.cend(), ','); 
cout << string(line.cbegin(), comma) << endl;

auto rcomma = find(line.crbegin(), line.crend(), ',');
// WRONG: will generate the word in reverse order

cout << string(line.crbegin(), rcomma) << endl;

FIRST,MIDDLE,LAST
then this statement would print TSAL!

// ok: get a forward iterator and read to the end of line

cout << string(rcomma.base(), line.cend()) << endl;

可以看到base 和不加base指向不同元素

iterator 到 reverse_iterator, reverse_iterator 会像前推一位

vector<int> v = { 0, 10, 20, 25,30,60};
ostream_iterator<int>os(cout, ",");
using RevIt = reverse_iterator<vector<int>::iterator>;
{
	const auto it = v.begin() + 3;
	RevIt r_it(it);
	cout << " base ";  *os = *it; cout << endl;
	//print 25; 
	
} {
	RevIt r_end(v.begin());
	RevIt r_begin(v.end());
	for (auto i = r_begin; i != r_end; i++) {
		*os++ = *i;
	}
	//60,30,25,20,10,0, 
	
}

(e). Structure of Generic Algorithms

Iterator Categories, 有五种: Each algorithm specifies what kind of iterator must be supplied 作为 its iterator parameters.

  1. Input iterator: Read, but not write; single pass, increment only
  2. Output iterator: Write, but not read; single pass, increment only
  3. Forward iterator: Read and write, multi-pass, increment only
  4. Birectional iterator: Read and write, multi-pass, increment and decrement
  5. Random-access iteator: Read and write, multi-pass, full iterator arithmetic
  • The standard specifies the minimum category for each iterator parameter of the generic and numeric algorithms: 算法需要iterator 的最小类别
    • 比如find 需要one pass, read-only traversal sequence, 最小需要 input iterator. 比如replace_copy 前两个iterator最小是Forward iterator, 第三iterator至少是output iterator
    • 如果传递iterator of a lesser power is an error
    • Many compilers will not complain when we pass the wrong category of iterator to an algorithm.

Input iterator 必须支持

  • 用于比较相等或者不等的运算符 ==, !=
  • Prefix and postfix increment (++)
  • Dereference operator *` to read element, dereference may appear only on the right-hand side of an assignment
  • arrow operator ->, same as (*it).member dereference the iterator and fetch member from underlying object

Input iterator 用于顺序访问(sequentially). *it++ is 保证 valid, 但是递增它可能导致所有其他指向流的iterator 失效, 因此no guarantee that we can save the state of input iterator and examine an element through that saved iterator. 只能用于single-pass algorithm, 例如find, accumulate, istream_iterator are input iterators.

Output iterator 可以想成Input iterator的补集. Output iterator must provide

  • Prefix and postfix increment ++
  • Dereference * 只能作为left-hand side of an assignment (Assigning to a dereferenced output iterator 就是 writes to the underlying element.)

copy 函数第三个iterator 就是output iterator, ostream iterator 也是 output iterator

Forward iterator:

  • can read and write a given sequence
  • move in only one direction through the sequence.
  • support all the operations of both input iterators and output iterators
  • they can read or write the same element multiple times
    • 可以save state of a forward iterator
  • 因为algorithm use forward iterator may make multiple passes through the sequence.
  • replace 要求 forward iterator, forward_list 也要求 forward iterator

Bidirectional iterators:

  • can read and write a sequence forward or backward
  • supporting all the operations of a forward iterator
  • support prefix and postfix decrement (--) operators
  • reverse 要求 bidirectional iterators
  • 除了forward_list 标准库的container 都满足 bidirectional iterator

Random-access iterators:

  • provide constant-time access to any position in the sequence.
  • support all the functionality of bidirectional iterators. 除此以外还支持
    • The relational operators (<, <=, >, and >=) to compare the relative positions of two iterators.
    • Addition and subtraction operators (+, +=, -, and -=) on an iterator and an integral value.
    • The subtraction operator (-) when applied to two iterators, which yields the distance between two iterators.
    • The subscript operator (iter[n]) as a synonym for * (iter + n).
  • sort 要求Random-access iterators, array, deque, string, vector, pointer when used to access elements of a built-in array are all random-access iteartor,

Algorithm Parameter Patterns

  • alg(beg, end, other args);
  • alg(beg, end, dest, other args);
    • dest is an iteartor that denotes a destination algorithm writes output. 假定safe to write as many elements as needed. assume the destination is large enough to hold the output.
    • 如果dest 是 iterator that refers directly to container, then algorithm 直接输出到容器exisiting elements. 更常见的是 dest is bound to an insert iterator or an ostream_iterator. Insert iterator 是add new elements to container 确保enough space. An ostream_iterator writes to an output stream, 也是不管写多少个元素都没有问题
  • alg(beg, end, beg2, other args);
    • beg2作为第二个输入范围的首元素, 此范围结束位置未指定. 算法假定从beg2 开始范围 与 begend所表示范围至少一样大
  • alg(beg, end, beg2, end2, other args);

Some Algorithms Use Overloading to Pass a Predicate: 比如

unique(beg, end); // uses the == operator to compare the elements

unique(beg, end, comp); // uses comp to compare the elements

Algorithms with _if Versions: Algorithms that take an element value typically have a second named (not overloaded) version that takes a predicate in place of the value. The algorithms that take a predicate have the suffix _if appended: 例子the find_if algorithm looks for a value for which pred returns a nonzero value.

find(beg, end, val); // find the first instance of val in the input range 

find_if(beg, end, pred); // find the first instance for which pred is true

Distinguishing Versions That Copy from Those That Do Not: algorithms that rearrange elements write the rearranged elements back into the given input range. These algorithms provide a second version that writes to a specified output destination. 同时也有一些算法同时提供_copy 和_if 版本

reverse(beg, end); // reverse the elements in the input range 

reverse_copy(beg, end, dest);// copy elements in reverse order into dest


// removes the odd elements from v1 ]

remove_if(v1.begin(), v1.end(),
    [](int i) { return i % 2; }); 

// copies only the even elements from v1 into v2; v1 is unchanged

remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i) { return i % 2; });

(f). Container-Specific Algorithms

  • listforward_list 定义了他们独有的sort, merge, remove, reverse, and unique, 因为generic version of sort requires random-access iterators, 因此sort 不能用于 listforward_list 因为他们offer bidirectional 和 forward iterators.
  • Generic version 其他的algorithm可以用于list,但是performance cost 代价太高, 比如A list can “swap” 快速交换元素 its elements by changing the links among its elements rather than swapping the values of those elements.
  • 对于list 和 forward list 优先考虑成员函数版本算法 而不是通用算法
  • list 和 forward list 还定义了 splice算法,
  • important difference between the list-specific and the generic versions is that the list versions change the underlying container.
  • merge and splice are destructive on their arguments(会销毁参数).例如generic merge将merged sequence to a given destination iterator; the two input sequences are unchanged. 而listmergedestroys (removed) the given list—elements from the argument list as they are merged into the object on which merge was called. After a merge, the elements from both lists continue to exist, but they are all elements of the same list.

都是从lst2移动到lst1





11. Associative Containers

(a). Overview

(1). Defining an Associative Container

  • 对于ordered container, map, multimap, set, and multiset. library uses the < operator for the key tyoe to compare the keys.
  • Key Types for Ordered Containers: 可以提供自己定义的比较操作代理关键字的< operator on keys. The specified operation must define a strict weak ordering over the key type
    • 两个关键词不能同时小于等于对方: 比如 如果 k1 <= k2, 那么不可以 k2 <= k1
    • 如果k1<=k2, k2<=k3, 那么 k1 <= k3
    • 如果两个Key, 任何都不小于等于另一个, 则成两个key, equivalent, 如果 k1 ==k2 and k2 == k3, 则 k1 == k3
  • 如果用自己定义的comparison operation. 需要pass data type and comparison type which is a function pointer type, 并且pass comparison function as constructor argument(类型必须与尖括号中类型一样 )

用自己定义的比较运算, must supply the type of that operation (pass as a constructor argument, the same type as specified inside the angle brackets) when we define the type of an associative container, 比如下面定义multiset, 定义两个types, 一个是Sales_data, 另一个是comparison type, which is a function pointer type that point to compareIsbn. 因为decltype返回的是函数类型,需要加上*表示函数指针, 注: 下面compareIsbn or &compareIsbn 都是可以的, 因为当函数作为parameter, 会自动转化为指针

bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{ 
    return lhs.isbn < rhs.isbn;
}
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);

//也可用&compareIsbn, 因为当函数作为argument时候,会自动转换成指针

multiset<Sales_data, decltype(compareIsbn)*> bookstore(&compareIsbn);

(2). Pair Type

The default pair constructor value initializes the data members, anon is a pair of two empty string and, line holds an empty string and an empty vector

pair<string, string> anon; // holds two strings 

pair<string, size_t> word_count; // holds a string and an size_t 

pair<string, vector<int>> line; // holds string and vector<int>

pair<string, string> author{"James", "Joyce"};

  • data members of pair are public. member are named first and second
  • function 可以用list intiailize(braced initializers) return value for pair return pair<string, int>();

(b). Associative Containers Operations

Syntax Description
key_type Type of key for this container type, 不是const
mapped_type Type associated with each key, map types only
value_type For sets, same as the key_type.
For maps, pair<const key_type, mapped_typed>
set<string>::value_type v1; // v1 is a string 

set<string>::key_type v2; // v2 is a string

map<string, int>::value_type v3; // v3 is a pair<const string, int> 

//cannot do 

v4["123"] = 456;//因为key is const 

map<string, int>::key_type v4; // v4 is a string, not const string 

v4 = "123";

map<string, int>::mapped_type v5; // v5 is an int

  • When we dereference an iterator, get a reference to a value of container’s value_type.
    • 对于map, value_type is a pair which first holds the const key and second holds the value. 可以改变value 值 但不能改变key的值
  • Iterators for sets Are const,虽然sets定义了iteratorconst_iterator 但都read-only access
  • map, multimap, set, or multiset, the iterators yield elements in ascending key order. 比如for loop 是按照升序来读(由小到大)
  • we do not use the generic algorithms (Chapter 10) with the associative containers. The fact that the keys are const means that we cannot pass associative container iterators to algorithms that write to or reorder container elements.
    • Associative containers can be used with the algorithms that read elements.However, many of these algorithms search the sequence. 因为elements in associative container 可以被快速找到根据他们的key, bad idea to use generic search algorithm, associative containers define a member named find, 用它比generic find algorithm 快的多(generic algorithm use sequential search)
    • 如果把associative container with algorithms either source sequence or as a destination.可以使用copy or inserter to bind an insert iterator (用inserter,可把associative container as a destination for another algorithm)
  • map 和 set, 只有key 不存在时候,才会真的插入,如果存在,对于set 无更改, map 会更改value
  • 对于multiset, multimap, insert 不需要return bool, 因为总是insert 成功, 返回an iterators to the new element
  • set 不支持下标运算, 也不能对 multimap 和 unordered_multi_map 进行下标运算 , 因为一个key 对应多个value
    • c[k]c.at[k] 区别是 at 有range check, 会throw out_of_range exception
    • 下标运算 和 at 只能用于non const 的 map, unordered_map
    • subscript a map, we get a mapped_type object; when we dereference a map iterator, we get a value_type object (有first, second)
  • multimap 和 multiset, multiple elements of a given key, those elements 是相邻的
// get an iterator to an element in word_count

auto map_it = word_count.begin();

// *map_it is a reference to a pair<const string, size_t> object

map_it->first = "new key"; // error: key is const 

++map_it->second; // ok: we can change the value through an iterator

iterator for set is const, 不能修改

set<int> iset = {0,1,2,3,4,5,6,7,8,9}; 
set<int>::iterator set_it = iset.begin(); 
if (set_it != iset.end()) {
    *set_it = 42; // error: keys in a set are read-only

    cout << *set_it << endl; // ok: can read the key 
    
}

insert into map

// four ways to add word to word_count 

word_count.insert({word, 1});
word_count.insert(make_pair(word, 1)); 
word_count.insert(pair<string, size_t>(word, 1)); 
word_count.insert(map<string, size_t>::value_type(word, 1));

Insert

Syntax Description
c.insert(v)
c.emplace(args)
对于map, set Returns a pair containing an iterator referring to the element with the given key and a bool 表示element 是否插入成功, false 表示之前就已经存在, 未插入; 对于multiset, multimap, 返回 iterator to the new element
c.insert(b,e)
c.insert(il)
return void. b,e is itertor, 对于map, set 如果key 不存在才会插入, multimap 和multiset inserts each element in the rage
c.insert(p,v)
c.emplace(p, args)
iterator p是一个插入位置的提示。returns an iterator to the element with the given key.

e.g. insert 是上面的version 1, 返回的是pair<iterator, bool>

map<string, size_t> word_count; 
string word;
while (cin >> word) {
    auto ret = word_count.insert({word, 1});
    if (!ret.second) 
        ++ret.first->second; 
}

Erase

Syntax Description
c.erase(k) removes 每一个等于k的key, Returns a size_type 表示删除元素的数量
c.erase(p) iterator p是一个删除的位置, 不能是c.end(), return an iterator after p or c.end()(if p is the last element)
c.erase(b,e) removes the range from [b,e), return e

Access

Syntax Description
c.find(k) return an iterator 指向第一个key = k的
c.count(k) returns the number of elements with k, 对于containers with unique keys, result always is 0 or 1
c.lower_bound(k) returns an iterator 指向第一个不小于k的元素
c.upper_bound(k) returns an iterator 指向第一个大于k的元素
c.equal_range(k) returns an pair of iterators denoting the elements with key k. 如果k 不存在,both members are c.end()

Access multimap

string search_item("Alain de Botton");
auto entries = authors.count(search_item); /
auto iter = authors.find(search_item); 
while(entries) {
    cout << iter->second << endl; 
    ++iter;
    --entries; 
}

可以用lower_bound, upper_bound 解决上面问题

for (auto beg = authors.lower_bound(search_item),
            end = authors.upper_bound(search_item); beg != end; ++beg)
cout << beg->second << endl; 

再用 equal_range 解决上面问题, equal_range 返回 等于查找元素的 range [beg,end)

for (auto pos = authors.equal_range(search_item);
pos.first != pos.second; ++pos.first)
    cout << pos.first->second << endl; 

(c). Unordered Containers

  • Rather than comparison operation to organize elements, these containers use a hash function and the key type’s == operator.
  • unordered container 的输出与 order container的输出会有不同

Manage the Buckets

  • The unordered containers are organized as a collection of buckets, each of which holds zero or more elements.
  • use hash function to map elements to bucekts. The container puts all of its elements with a given hash value into the same bucket.
  • To access an element, container first computes the element’s hash code, which tells which bucket to search.

  • The hash function must always yield the same result when called with the same argument. Ideally, the hash function also maps each particular value to a unique bucket. However, a hash function is allowed to map elements with differing keys to the same bucket
    • When a bucket holds several elements, those elements are searched sequentially to find the one we want.
    • However, if the bucket has many elements, many comparisons may be needed to find a particular element.
  • 如果container 允许重复elements with a given key (multi_unordered_map, multi_unordered_set), all the elements with the same key will be in the same bucket.
  • The performance of an unordered container depends on the quality of its hash function and on the number and size of its buckets.
  • load_factor = size(#elements) / bucket_count; max_load_factor被用作threshold that forces an increase in the number of buckets. max_load_factor越小, 更频繁call rehash
  • bucket_count是现在有几个可以使用,不是有几个已经被占用, max_bucket_count是最大的potential number of buckets that unordered_map can have (受system constraints or limitation 影响而不同)

Difference between rehash and reserve: rehash doesn’t give you any guarantees, and reserve doesn’t express the purpose of rehashing. Use rehash if you think your map is inefficient, and reserve if you’re preparing for a lot of insertions.

  • rehash takes an existing map and rebuilds a new size of buckets, rehashing in the process and redistributing elements into the new buckets.
    • If n >= current buckets into the container then rehashed is done. The new bucket count can be greater than or equal to n.
    • If n < current buckets then there may or may not be any effect of function. It totally depends upon compiler
  • reserve guarantees you that if you don’t insert more than the reserved number of elements, there will be no rehashing (i.e. your iterators will remain valid). Effectively calls rehash(std::ceil(count / max_load_factor())). reserve 最好在插入元素前call

比如有code, 下面是okay的

auto itr = myMap.begin();
while (itr != myMap.end()) {
    if (/* removal condition */) {
        itr = myMap.erase(itr);
    } else {
        ++itr;
    }
}

The erase members shall invalidate only iterators and references to the erased elements, and preserve the relative order of the elements that are not erased. 而Rehashing invalidates iterators, changes ordering between elements.

Requirements on Key Type for Unordered Containers

  • use an object of type hash<key_type> to generate the hash code for each element.
  • library 定义一些hash template for built-in types, including pointers. 同样也定义了hash for some library types including strings and the smart pointer types. we can directly define unordered containers whose key is one of the built-in types (including pointer types), or a string, or a smart pointer.
  • 我们不能直接定义unordered container that uses our own class types as key type. Must supply our own version of hash template.

To use Sales_data as the key, we’ll need to supply functions to replace both the == operator and to calculate a hash code. 如果我们class 内有 == operator we can override just the hash function

size_t hasher(const Sales_data &sd) { 
    return hash<string>()(sd.isbn());
}
bool eqOp(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn();
}

using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
// arguments are the bucket size, pointers to the hash function, and equality operator

SD_multiset bookstore(42, hasher, eqOp);//42 is bucket size 


// 方法二: use FooHash to generate the hash code; Foo must have an == operator 

unordered_set<Foo, decltype(FooHash)*> fooSet(10, FooHash);




12. Dynamic Memory

  • Local, automatic objects are created and destroyed when the block in which they are defined is entered and exited
  • Local static objects are allocated before their first use and are destroyed when the program ends
  • Dynamically allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed.只有显示的释放, 才能被销毁

Our programs have used only static or stack memory.

  • Objects allocated in static or stack memory are automatically created and destroyed by the compiler.
    • Static memory is used for local static objects(局部静态变量, 见6.1, 程序执行路径第一次经过时define exist until 程序结束), for class static data members, and for variables defined outside any function
      • static objects are allocated before they are used, and they are destroyed when the program ends.
    • Stack memory is used for nonstatic objects defined inside functions (local variables, also hold parameters passed to functions).
      • Stack objects exist only while the block in which they are defined is executing;
      • Stack similar to std::stack class, FIFO, the function knows 他们期望的parameters can be found on the end of the stack. Function can push locals on stack and pop before returning the function.
      • Stack is usually in CPU cache, so operations involving objects stored on it tend to be faster (因为FIFO design)
      • However, stack is limited resource, Running out of stack is called stack buffer overflow, 不会遇到这样问题除非有crazy recursive function
  • In addition to static or stack memory,This memory is referred to as the free store or heap.
    • Programs use the heap for objects that they dynamically allocate—that is, for objects that the program allocates at run time
    • The program controls the lifetime of dynamic objects; our code must explicitly destroy such objects when they are no longer needed.
    • Running out of heap memory result in std::bad_alloc

Dynamic Memory and Smart Pointers:

  • newdelete error-prone, cannot rely on the default definitions for the members that copy, assign, and destroy class objects
    • new : allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object;
    • delete: takes a pointer to a dynamic object, destroys that object, and frees the associated memory.
  • Dynamic memory is problematic because it is surprisingly hard to ensure that we free memory at the right time. 有时忘记释放内存,会造成memory leak, 或者尚有指针引用存储的情况下就释放了它, 造成pointer is invalid.
  • To make using dynamic memory easier (and safer), new library定义了two smart pointer, A smart pointer acts like a regular pointer with the important exception that it automatically deletes the object to which it points.

(a). Managing Memory Directly

  1. Using new to Dynamically Allocate and Initialize Objects
  2. Dynamically Allocated const Objects
  3. Memory Exhaustion

(1). Using new to Dynamically Allocate and Initialize Objects

  • Objects allocated on the free store are unnamed(没有命名的), Instead, new returns a pointer to the object it allocates:
  • By default, dynamically allocated objects are default initialized: built-in or compound type objects have undefined value; objects of class type are initialized by their default constructor
  • 可以initialize a dynamically allocated object using 直接初始化. 可以用parentheses 的constructor(in place construct) or 使用list initialization(curly braces), 也可以用value initialization, 只需要在类型别名后跟一个空括号
    • 对于class type that define their own constructor, value initialization 是没有意义的. 因为不管用什么形式, 都用default constructor 来初始化
    • 对于built-in type, 有difference, a value-initialized object of built-in type has a well-defined value but a default-initialized object does not default-initialized 是未定义的
  • 可以用auto 去deduce the type
int *pi = new int; // pi points to a dynamically allocated, unnamed, uninitialized int

string *ps = new string; // initialized to empty string

int *pi = new int; // pi points to an uninitialized int 未初始化

初始化

int *pi = new int(1024); // object to which pi points has value 1024 

string *ps = new string(10, '9'); // *ps is "9999999999"

// vector with ten elements with values from 0 to 9

vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};

value initialization

string *ps1 = new string; // default initialized to the empty string 

string *ps = new string(); // value initialized to the empty string 

int *pi1 = new int; // default initialized; *pi1 is undefined 

int *pi2 = new int(); // value initialized to 0; *pi2 is 0

pointer of class type initialization When we provide an initializer inside parentheses, we can use auto to deduce the type of the object we want to allocate from that initializer(第一个例子). 如果 obj 是int, 那么 p1 是 int*, 如果 obj string, 那么 p1 是 string*

auto p1 = new auto(obj); // p points to an object of the type of obj // that object is initialized from obj

auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer, 只能用括号包含单个初始器


(2). Dynamically Allocated const Objects

  • It is legal to use new to allocate const objects:
  • A dynamically allocated const object must be initialized
  • A const dynamic object of a class type that defines a default constructor may be initialized implicitly. Objects of other types must be explicitly initialized. A const dynamic object of a class type that defines a default constructor 可以隐式初始化, 其他对象必须显示初始化
  • Because the allocated object is const, the pointer returned by new is a pointer to const
// allocate and initialize a const int

const int *pci = new const int(1024); 

// allocate a default-initialized const empty string const 

string *pcs = new const string;

(3). Memory Exhaustion

  • Although modern machines tend to have huge memory capacity, 可是也有可能自由空间(free store)被耗尽(exhausted). Once a program has used all of its available memory, new expressions will fail.
  • if new is unable to allocate the requested storage, it throws an exception of type bad_alloc,
    • 可以prevent new from throwing an exception by using a different form of new
    • 下面例子阻止抛出异常叫: placement new, 传递一个由标准库定义的 nothrow 对象, 如果将nothrow 传递给 new, 我们是告诉它不要抛出异常, 如果new不能分配内存, 返回一个空指针.
    • bad_allocnothrow 都定义在new header中

阻止抛出bad_alloc

// if allocation fails, new returns a null pointer

int *p1 = new int; // if allocation fails, new throws std::bad_alloc

int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer

(4). Freeing Dynamic Memory

  • 为了避免 memory exhaustion, we must return dynamically allocated memory to the system once we are finished using it through delete expression
  • delete p;: p must point to a dynamically allocated object or be null
  • Deleting a pointer to memory that was not allocated by new, or deleting the same pointer value more than once, is undefined
    • 删除non dynamically allocated 指针是错误的. Compiler cannot tell whether a pointer 指向statically or dynamically allocated object.
    • 如果两个指针指向一个object, 删除第一个指针, 不用删除第二个指针(delete more than once), 否则error
    • Compiler cannot tell whether memory addressed by a pointer has already been freed. Most compilers will accept these delete expressions, even though they are in error.
  • 对于shared_pointer 管理的内存在最后一个shared_ptr销毁时被自动释放, 但对于built-in pointer 是不是这样的, A dynamic object managed through a built-in pointer (rather than smart pointers) exists until it is explicitly deleted (freed).
    • Functions that return pointers (rather than smart pointers) to dynamic memory put a burden on their callers—the caller must remember to delete the memory
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;

delete i; // error: i is not a pointer

delete pi1; // undefined: pi1 refers to a local (指的是局部静态对象)

delete pd; // ok

delete pd2; // undefined: the memory pointed to by pd2 was already freed 

delete pi2; // ok: it is always ok to delete a null pointer

对于function 返回指针指的是dynamically allocated object. function 的caller 必须释放内存, 比如下面的例子, pointer out of scope, 但是内存还没有释放

Foo* factory(T arg)
{
    return new Foo(arg); // caller is responsible for deleting this memory 

}

void use_factory(T arg){
// use p but do not delete it

    Foo *p = factory(arg);
} // p goes out of scope, but the memory to which p points 没有被释放


//正确方式
void use_factory(T arg){
    Foo *p = factory(arg);
    delete p;
} 


Managing Dynamic Memory Is Error-Prone: can avoid all of these problem by using smart pointers exclusively.

  1. 忘记delete内存 – 内存泄漏问题, memory is never returned to the free store, 查找内存泄漏错误是困难的, 因为通常程序运行很长时间后,真正耗尽内存是,才能检测到这种错误
  2. Using an object after it has been deleted. This error can sometimes be detected by making the pointer null after the delete.
  3. 同一块内存释放两次. This error can happen when two pointers address the same dynamically allocated object. If delete is applied to one of the pointers, then the object’s memory is returned to the free store. If we subsequently delete the second pointer, then the free store may be corrupted.

(5).Resetting the Value of a Pointer after a delete …

  • delete pointer, pointer becomes invalid. 尽管pointer is invalid, pointer 继续hold the address of the dynamic memory. After the delete, the pointer becomes what is referred to as a dangling pointer. 即曾经保存对象数据 但现已经无效的内存指针
    • Dangling pointers have all the problems of uninitialized pointers. 有种方法可以避免问题: 即 If need to keep the pointer after delete memory 在离开现scope前assign nullptr to the pointer after we use delete. 这样清楚指出指针不指向任何对象

下面例子中p 和 q, 指向the same dynamically allocated object. We delete that memory and set p to nullptr, 表示 that the pointer no longer points to an object. However, resetting p has no effect on q, which became invalid when we deleted the memory to which p

int *p(new int(42)); // p points to dynamic memory

auto q=p; // p and q point to the same memory 

delete p; // invalidates both p and q

p = nullptr; // indicates that p is no longer bound to an object

(b). shared_ptr

  • A default initialized smart pointer holds a null pointer;shared_ptr<string> p1;

shared_ptrunique_ptr 都支持的操作

Syntax Description
shared_ptr<T> sp
shared_ptr<T> up
Null smart pointer that can point to objects of type T
p 将p用作一个条件判断, 若p 指向一个对象, 则为true
*p Dereference p to get the object to which p points
p->mem same as (*p).mem
p.get() 返回p中保存的指针, 要小心使用,如果指针指针释放了其对象, 返回指针所指向的对象就消失了
swap(p,q)
p.swap(q)
交换p和q的指针

shared_ptr 独有的操作

Syntax Description
make_shared<T>(args) 返回一个shared_ptr, pointing to a dynamically allocated object of type T. Use args to initialize that object
shared_ptr<T>p(q) p is a copy of shared_ptr q, 递增q中的计数器, The pointer in q must be convertible to T*
p = q p 和 q 都是 shared_ptr, 所保存的指针必须能相互转换,Decrement p’s reference count and increment q’s count; delets p’s existing memory if p’s count goes to 0
p->unique p.use_count为1,返回true, 否则返回false
p.use_count 返回与p 共享对象的智能指针数量, 可能很慢, intended primarily for debugging purposes

(1). make_shared

  • The safest way to allocate and use dynamic memory is to call a library function named make_shared.
  • This function allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object
  • defined in memory header
  • 类似于emplace, 可以用 its argument to construct an object of the given type in place
  • 可以用auto 来存储 make_sharedshared_ptr
// shared_ptr that points to an int with value 42

shared_ptr<int> p3 = make_shared<int>(42);

// p4 points to a string with value 9999999999 

shared_ptr<string> p4 = make_shared<string>(10, '9');

// p6 points to a dynamically allocated, empty vector<string> 

auto p6 = make_shared<vector<string>>();

Copying and Assigning shared_ptrs

  • copy or assign a shared_ptr, each shared_ptr keeps track of how many other shared_ptrs (reference count) point to the same object:
    • 当use shared_ptr to initialize another shared_ptr, 或者 pass it to or return it from a function by value, reference count 都会增加
    • 当assign a new value to shared_ptr 或者shared_ptr 被destoryed, 或者goes out of scope 时候, counter 会decrement.
  • shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages
auto p = make_shared<int>(42); // object to which p points has one user 

auto q(p); // p and q point to the same object

// object to which p and q point has two users

auto r = make_shared<int>(42); // int to which r points has one user 

r = q; // assign to r, making it point to a different address

    // increase the use count for the object to which q points 

    // reduce the use count of the object to which r had pointed

    // the object r had pointed to has no users; that object is automatically freed

(3).shared_ptrs Automatically Destroy Their Objects

  • When the last shared_ptr pointing to an object is destroyed, shared_ptr 会自动销毁此对象。It does so through destructor.
    • 比如string 的constructuor 会分配空间来保存character that compose string. destructor 会free memory. 同样的, vector allocate memory to hold elements in vector, desctructor destory elements and free memory used for the elements.
  • The destructor for shared_ptr decrements the reference count of the object to which that shared_ptr points. If the count 变成0, the shared_ptr destructor 销毁 shared_ptr指的对象 and frees the memory used by that object.
  • 如果把shared_ptr 放进一个容器中, 如果不需要使用, call erase 删除不再需要的那些元素.

下面例子中当p 销毁时,递减其引用计数并检查是否为0,因为p是唯一引用factory 返回内存的对象。由于p将要销毁,p指向的这个对象也被销毁, 所占用的内存会被释放.

shared_ptr<Foo> factory(T arg)
{
    return make_shared<Foo>(arg);
}

void use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
} // p goes out of scope; the memory to which p points is automatically freed

对于下面例子,returns a copy of p to its caller. Copying a shared_ptr adds to the reference count of that object. Now when p is destroyed, there will be another user for the memory to which p points. Memory itself will not be freed.

shared_ptr<Foo>  use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
    return p;
} 

(4). Classes with Resources That Have Dynamic Lifetime

Programs tend to use dynamic memory for one of three purposes:

  1. They don’t know how many objects they’ll need 不知道自己需要使用多少对象
  2. They don’t know the precise type of the objects they need, 不知道所需对象准确类型
  3. They want to share data between several objects 需要在多个对象空间共享操作

e.g. 使用shared_ptr, 比如vector,allocated resources that exist 与对象生存期一致, 但我们想定义对象 allocated resources 与对象生存期是独立的

vector<string> v1; 
{ 
    vector<string> v2 = {"a", "an", "the"};
    v1 = v2; // copies the elements from v2 into v1 

} // v2 is destroyed, 但是v1 仍有三个元素,


Blob<string> b1;
{
    Blob<string> b2 = {"a", "an", "the"};
    b1 = b2;
}// 想让b2 被销毁时,但是b2中的元素不能销毁

//b1 指向最初由b2创建的元素

因为template 在后面用到, 先用string类

class StrBlob{
public: 
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string>il);
    
    size_type size() const {return data->size;}
    bool empty() const {return data->empty();}
    void push_back(const string & t) {data->push_back(t);}
    void pop_back();
    string& front();
    string& back();

private: 
    shared_ptr<<vector<string>> data; 
//没有 copy constructor, data 也能被定义, 因为compiler generate copy constructor
    
    void check(size_type i, const string & msg) const; 
};

StrBlob::StrBlob(): data(make_shared<vector<string>>()) {} 

StrBlob::StrBlob(initializer_list<string>il): data(make_shared<vector<string>>(il)) {} 

void StrBlob::check(size_type i, const string & msg) const {
    if (i>= data->size())
        throw out_of_range(msg);
}; 

(5). Using shared_ptrs with new

  • if we do not initialize a smart pointer, it is initialized as a null pointer.
  • The smart pointer constructors that take pointers are explicit. Cannot implicitly convert a built-in pointer to a smart pointer. 必须使用direct form of initialization
    • a function that returns a shared_ptr cannot implicitly convert a plain pointer in its return statement
  • a pointer used to initialize a smart pointer must point to dynamic memory because, by default, smart pointers use delete to free the associated object.
  • reset 通常与 unique 一起使用, 控制多个shared_ptr 共享的对象. Before changing the underlying object, we check whether we’re the only user. If not, we make a new copy before making the change, 详见下面例子
  • 当用shared_ptr with dynamic allocated array, 必须用deleter, 否则删除的只是array的第一个元素
shared_ptr<double> p1; // shared_ptr that can point at a double 

shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42


shared_ptr<int>p1 = new int(1024);//error: must use direct initialization

shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization

function return

shared_ptr<int> clone(int p) {
    return new int(p); // error: implicit conversion to shared_ptr<int> 

}

shared_ptr<int> clone(int p) {
    // ok: explicitly create a shared_ptr<int> from int*

    return shared_ptr<int>(new int(p));
}

Define and Change shared_ptr

Syntax Description
shared_ptr<T>p(q) p manages the object to which the built-in pointer q points; q must point to memory allocated by new and must be convertible to T*
shared_ptr<T> p (u) p assumes ownership from the unique_ptr u(接管u接管了对象所有权); makes u null (将u设置成null)
shared_ptr<T> p (q, d) p assumes ownership from the object to which built-in poiner q points. q must be convertible to T*, p will use the callable object d(lambda expression) in place of delete to free q (call d 代替 delete )
shared_ptr<T>p (p2, d) p is a copy of shared_ptr p2 (递增p2的计数器) except that p uses the callable object d in place of delete
p.reset()
p.reset(q)
p.reset(q,d)
若p is only shared_ptr pointing at its object, reset frees p’s existing object. 若传递了可选的参数built-in pointer q, makes p pointer to q, otherwise makes p null. If d is supplied will call d to free q otherwise uses delete to free q

reset + unique

if (!p.unique()) //true 表示p不是唯一控制underlying object的
    
    p.reset(new string(*p)); // 我们不是唯一用户, 分配新的拷贝

*p += newVal; // now that 我们知道自己是唯一用户, okay to change this object

当用shared_ptr with dynamic allocated array, 必须用deleter

shared_ptr<Dog>p1(new Dog[3], [](Dog * p){delete[] p;});

Don’t Mix Ordinary Pointers and Smart Pointers

  • A shared_ptr can coordinate destruction only with other shared_ptrs that are copies of itself(当只有自身拷贝,可以call destructor). 这也是推荐使用 recommend using make_shared rather than new. make_shared we bind a shared_ptr to the object at the same time that we allocate it. 从而避免了将同一块内存绑定到多个独立创建的shared_ptr
  • It is dangerous to use a built-in pointer to access an object owned by a smart pointer, because we may not know when that object is destroyed.
  • When we bind a shared_ptr to a plain pointer, we give responsibility for that memory to that shared_ptr. we should no longer use a built-in pointer to access the memory to which the shared_ptr now points

下面例子 parameter to process is passed by value.

  • 正确方式是 pass it a shared_ptr 因此 argument to process is copied into ptr. Copying a shared_ptr increments its reference count. Thus, inside process the count is at least 2. When process completes, ptr reference count 会递减, 但不会变为0,因此当局部变量ptr被销毁时, the memory to which ptr points will not be deleted。
  • 错误的方法二: 传递了一个临时的shared_ptr that we explicitly construct from a built-in pointer. 但是会有error, 因为pass的是temporary. That temporary is destroyed when the expression in which the call appears finishes. Destroying the temporary decrements the reference count, which goes to zero. 临时对象被销毁时,所指的内存会被释放. But x continues to point to that (freed) memory; x is now a dangling pointer. Attempting to use the value of x is undefined.
// ptr is created and initialized when process is called 

void process(shared_ptr<int> ptr)
{
    // use ptr

} // ptr goes out of scope and is destroyed


shared_ptr<int> p(new int(42)); // reference count is 1

process(p); // copying p increments its count; in process the reference count is 2

int i = *p; // ok: reference count is 1



int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer

process(x); // error: cannot convert int* to 

shared_ptr<int> process(shared_ptr<int>(x)); // legal, but the memory will be deleted!

//shared_ptr<int>(x) 是 r-value 在function完之后, 就被删除掉了, 明天记得debug看什么时候删除的

int j = *x; // undefined: x is a dangling pointer!

Don’t Use get to Initialize or Assign Another Smart Pointer

  • smart pointer 定义了一个函数 get, 返回built-in pointer to the object that smart pointer is managing. 此函数是为了这样一种情况设计的:
    • This function is intended for cases when we need to pass a built-in pointer to code that can’t use a smart pointer. The code that uses the return from get must not delete that pointer.
  • Although the compiler will not complain, it is an error to bind another smart pointer to the pointer returned by get

下面例子中, p 和 q是相互独立的创建的, 各自的计数器都是1, 当q所在block结束时, q被destroyed -> free the memory to which q points. makes p into a dangling pointer. 意味着attempt to use p is undefined. Moreover, when p is destroyed, the pointer to that memory will be deleted a second time.

shared_ptr<int> p(new int(42)); // reference count is 1

int *q = p.get(); // ok: but don't use q in any way that might delete its pointer

{ 
    // undefined: 两个独立的shared_ptr指向相同的内存
    
    shared_ptr<int>(q);

} // block ends, q is destroyed, and the memory to which q points is freed

int foo = *p; // undefined; the memory to which p points was freed

(6). Smart Pointers and Exceptions

  • Need to ensure that resources are properly freed if an exception occurs. One easy way to make sure resources are freed is to use smart pointers.
  • When we use a smart pointer, the smart pointer class ensures that memory is freed when it is no longer needed even if the block is exited prematurely:
    • When a function is exited, 正常处理结束或者发生了异常, 无论哪种情况 all the local objects are destroyed
    • 如果使用built-in pointer 管理内存, 在new之后对应的delete发生异常, 内存不会自动释放
  • classes that are designed to be used by both C and C++ generally require the user to specifically free any resources that are used.

下面第一个例子 即使有异常, 内存会自动被释放, 第二个例子, 如果异常在newdelete之间且异常未被catch, this memory can never be freed

void f()
{
shared_ptr<int> sp(new int(42)); // allocate a new object

// code that throws an exception that is not caught inside f 

} // 函数结束时 shared_ptr 自动释放内存


void f()
{
int *ip = new int(42); // dynamically allocate a new object

 // code that throws an exception that is not caught inside f

delete ip; // free the memory before exiting

}

Classes that allocate resources—and that do not define destructors to free those resources—can be subject to the same kind of errors that arise when we use dynamic memory. 比如下面例子如果connection had a destructor, that destructor would automatically close the connection when f completes.问题与memory leak 几乎是等价的. 解决方法用shared_ptr + delete, 定义一个函数代替delete, 即使发生异常, 也可以正确的关闭

struct destination; // 表示我们正在连接什么

struct connection; // 表示我们连接所需要的信息

 connection connect(destination*); //打开连接 
 
 void disconnect(connection); // 关闭连接
 
 void f(destination &d /* other parameters */)
{
// 获得连接,接住使用完后要关闭它

connection c = connect(&d);

// use the connection

// 如果在f退出前忘记调用disconnect, 就无法关闭c了
}

//解决方法

void end_connection(connection *p) { disconnect(*p); }

void f(destination &d /* other parameters */)
{
    connection c = connect(&d);
    shared_ptr<connection> p (&c, end_connection);
    //使用连接

    //当f 退出时(即使是由于异常退出), connection会被正确关闭
    
}

Smart Pointer Pitfalls

  1. 不使用相同的built-in pointer value to initialize more than one smart pointer
  2. 不delete get 返回的指针
  3. Don’t use get() to initialize or reset another smart pointer.(不把get返回的指针跟其他smart pointer 绑定)
  4. If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away.
  5. 如果使用smart pointer to manage a resource other than memory allocated by new, remember to pass a deleter

(c). unique_ptr

  • Unlike shared_ptr, only one unique_ptr at a time can point to a given object 只能有一个指定给定对象
  • The object to which a unique_ptr points is destroyed when the unique_ptr is destroyed
  • Unlike shared_ptr, there is no library function comparable to make_shared that returns a unique_ptr. 当定义unique_ptr 时, 需要将其绑定到一个new 返回的指针上
  • As with shared_ptrs, we must use the direct form of initialization:
  • 不能copy unique_ptr, cannot assign 一个 unique_ptr 给另一个 unique_ptr
    • 虽然不能copy, 但可以调用release(返回当前保存的指针 并将其设为null, 切断unique_ptr和它原来管理的对象联系) or reset
      • release 返回的通常用来initialize or assign 另一个smart pointer
  • one exception to the rule that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed . 最常见的例子是 when we return a unique_ptr from a function:
    • 较早版本有个auto_ptr, 具有unique_ptr 部分特性, 但是不能在容器中保存auto_ptr, 也不能返回auto_ptr. 虽然auto_ptr仍是标准库的一部分, 但程序应该使用unique_ptr
  • Differ from shared_ptr, we must supply the deleter type inside the angle brackets for unique_ptr

unique_ptr Operations

Syntax Description
unique_ptr<T>u1 Null unique_ptr that can point to objects of type T. u1 will use delete to free its pointer
unique_ptr<T,D>u2 u2 will use a callable object of type D to free its pointer
unique_ptr<T,D>u(d) Null unique_ptr that point to objects of type T that uses d, which must be an object of type D inplace of delete
u = nullptr Deletes the object to which u points, makes u null
T* p = u.release() Relinquishes control of the pointer u had held; returns the pointer u had held and makes u null
u.reset()
u.reset(q)
u.reset(nullptr)
Deletes the object to which u points; If the built-in pointer q is supplied, makes u point to that object. Otherwise makes u null
unique_ptr<double> p1; // unique_ptr that can point at a double 

unique_ptr<int> p2(new int(42)); // p2 points to int with value 42

unique_ptr<string> p1(new string("Stegosaurus")); 
unique_ptr<string> p2(p1); // error: no copy for unique_ptr 

unique_ptr<string> p3;
p3 = p2; // error: no assign for unique_ptr

nullptr 没有 allocate memory 不能dereference

unique_ptr<int>pt;
*pt = 0;//error 因为nullptr 并没有allocate memory

unique_ptr<int>pt(new int(0));
*pt = 0;
    

reset / release

// transfers ownership from p1 to p2 

unique_ptr<string> p2(p1.release()); // release makes p1 null

unique_ptr<string> p3(new string("Trex"));

// 将所有权从p3 转移给 q2

p2.reset(p3.release()); // reset 释放了p2原来的内存, 将p3指针所有权转移给p2 并将p3设置为空,

copy unique pointer from function return. 下面两个例子, compiler knows 返回的对象将要被销毁, 在此情况下, the compiler does a special kind of “copy” which we’ll discuss in § 13.6.2.

unique_ptr<int> clone(int p) {
// ok: explicitly create a unique_ptr<int> from int*

    return unique_ptr<int>(new int(p));
}

//can return a copy of local object

unique_ptr<int> clone(int p) { 
    unique_ptr<int> ret(new int (p)); 
    // . . .
    
    return ret;
}

deleter

// p points to an object of type objT and uses an object of type delT to free that object

// it will call an object named fcn of type delT 

unique_ptr<objT, delT> p (new objT, fcn);

void f(destination &d /* other needed parameters */)
{
    connection c = connect(&d); // open the connection 
    
    // when p is destroyed, the connection will be closed

    unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
    // when f exits, even if by an exception, the connection will be properly closed 
    
}

(d). weak_ptr

  • A weak_ptr is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr
    • Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr. 当最后一个指向对象的 shared_ptr 被销毁,对象就被deleted. That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.”
  • 创建weak_ptr 需要shared_ptr 来初始化
  • Because the object might no longer exist, we cannot use a weak_ptr to access its object directly. To access that object, we must call lock. The lock function checks whether the object to which the weak_ptr points still exists.如果存在,lock returns a shared_ptr to the shared object, 也保证所指向的底层对象也会存在
Syntax Description
weak_ptr<T>w Null weak_ptr that can point at objects of type T
weak_ptr<T>w(sp) weak_ptr that points to the same object as the shared_ptr sp. T must be convertible to the type to which sp points.
w = p p can be shared_ptr or a weak_ptr. After the assignment w shares ownership with p
w.reset() Makes w null
w.use_count() The number of shared_ptrs that share ownership with w
w.expired() Returns true if w.use_count() is zero, false otherwhise
w.lock() If expired is true, returns a null shared_ptr; otherwise returns a shared_ptr to the object to which w points

注意当绑定p 到 wp, use count 不变

auto p = make_shared<int>(42); 
weak_ptr<int>wp(p); // wp weakly shares with p; use count in p is unchanged

use lock, 下面语句中np访问对象是安全的

if ( (shared_ptr<int> np = wp.lock()) ) {   // true if np is not null

// inside the if, np shares its object with p 

}

应用 define a companion pointer class for our StrBlob class, 注意下面class StrBlobPtr 不能绑定到const StrBlob 对象, 因为 that the constructor takes a reference to a nonconst object of type StrBlob.

class StrBlobPtr; //对于StrBlob中的友元声明来说, 前置声明是必要的

class StrBlob{//具体定义在上面的shared_ptr,这里加了个friend class

    friend class StrBlobPtr; //因为StrBlobPtr 要access StrBlob private member Data

    StrBlobPtr begin() {return StrBlobPtr(*this); }
    StrBlobPtr end() {
        auto ret =  StrBlobPtr(*this, data->size()); 
        return ret;
    }
};

class StrBlobPtr{
public:
    StrBlobPtr(): cur(0) {} 
    StrBlobPtr(StrBlob & a, size_t sz = 0): wptr(a.data), curr(sz) {}
    std::string& deref() const; 
    StrBlobPtr& incr(); //前缀递增

private:
    std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
    //保存一个weak_ptr, 意味着底层vector可能被销毁

    std::weak_ptr<std::vector<std::string>> wptr;

    std::size_t curr; //在数组中当前位置

};

//StrBlobPtr check 与 StrBlob check不同,需要检查指针是否存在

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(
    std::size_t i, const std::string& msg) const
{
    auto ret = wptr.lock(); //vector还存在吗?

    if(!ret) throw std::runtime_error("unbound StrBlobPtr");
    if(i>=ret->size())
        throw std::out_of_range(msg);
    return ret; //返回指向vector的shared_ptr
    
}

std::string& StrBlobPtr::deref() const{
    auto p = check(curr, "deference past end");
    return (*p)[curr]; //(*p)解引用获取vector
    
}

StrBlobPtr& StrBlobPtr::incr(){
    // if curr already points past the end of the container, can't increment it 
    
    check(curr, "increment past end of StrBlobPtr"); 
    
    ++curr; // advance the current state

    return *this;
}

(e). Dynamic Arrays

  • new and delete operators allocate objects one at time 一次只能分配释放一个
  • 例如vector 和string, 有时候需要一次为很多对象分配内存. C++ 定义了两种 an array of objects at once(一次分配一个对象数组)
    • new :
    • allocator let us separate allocation from initialzation. Use allocator better performance and more flexible memory management
  • Most applications should use a library container rather than dynamically allocated arrays. Using a container is easier, less likely to contain memory- management bugs, and is likely to give better performance.
    • Classes that use the container 可以copy, assignment, and destruction. Classes that allocate dynamic arrays must define their own versions of these operations to manage the associated memory

(1). new and Arrays

  • new allocates the requested number of objects and (assuming the allocation succeeds) returns a pointer to the first one
    • can also use type alias to represent an array type
  • Allocating an Array Yields a Pointer to the Element Type(not array type): 当我们用new 来allocate 一个数组时, 并没有得到一个array type. we get a pointer to the element type of the array
    • 不能call begin, end on a dynamic array (因为这些function use the array dimension(part of array’s type) to return pointers). 同时也不能用 a range for 来process the elements in a (so-called) dynamic array
  • By default, objects allocated by new—whether allocated as a single object or in an array—are default initialized(没有default constructor 不能dynamically allocated as array). 可以对数组中元素进行value initialization, 方法是size后面 加一个空括号
    • 新标准下, 可以provide a braced list of element initializers. 如果fewer initializers than elements, remaining elements are value initialized. If there are more initializers than the given size, then the new expression fails and no storage is allocated. In this case, new throws an exception of type bad_array_new_length(in new header).
    • Although we can use empty parentheses to value initialize the elements of an array, we cannot supply an element initializer inside the parentheses. So cannot use auto to allocate an array
  • It Is Legal to Dynamically Allocate an Empty Array
    • When we use new to allocate an array of size zero, new returns a valid, nonzero pointer. That pointer is guaranteed to be distinct from any other pointer returned by new. 就像off-the-end pointer for a zero-element array. can use this pointer in ways that we use an off-the-end iterator
      • The pointer cannot be dereferenced—after all, it points to no element
  • To free a dynamic array, we use a special form of delete - 在指针前加上一个空方括号
    • Elements in an array are destroyed in reverse order. That is, the last element is destroyed first, then the second to last, and so on.
    • delete a pointer to an array, empty bracket pair is essential: 指示compiler 此指针指向一个对象数组的第一个元素. 如果忽略方括号 or 在删除a pointer to an object(not array)时候provide 方括号, behavior is undefined.
    • The compiler is unlikely to warn us if we forget the brackets when we delete a pointer to an array or if we use them when we delete a pointer to an object. Instead, our program is apt to misbehave without warning during execution(行为异常).

对于type alias 即使没有方括号, the compiler executes this expression using new[]. Compiler executes as if we write int *p = new int[42];

// call get_size 确定分配多少个int

int *pia = new int[get_size()]; // pia points to the first of these ints

//type alias

typedef int arrT[42]; // arrT names the type array of 42 ints

int *p = new arrT; // allocates an array of 42 ints; p points to the first one

initialize

int* pia = new int[10];  //10个未初始化的int

int *pia2 = new int[10](); //10个值初始化为0的int

string *psa = new string[10];//10个空string

string *psa2 = new string[10](); // 10个空string


//可以提供braced list of initializer

//10个int 分别对应 corresponding initializer

int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};

// block of ten strings; the first four are initialized from the given initializers

// remaining elements are value initialized

string *psa3 = new string[10]{"a", "an", "the", string(3,'x')};

size zero: 对于下面的p 如果n = 0, p 可以加 0, 也可以用 p-p = 0 (subtract the pointer from itself, yielding zero). for loop 条件会失败, loop 不会执行

size_t n = get_size(); // get_size returns the number of elements needed 

int* p = new int[n]; // allocate an array to hold the elements

for (int* q = p; q != p + n; ++q)
    /* process the array */ ;

delete

delete p; //p must point to a dynamically allocated object or be null

delete [] pa; // pa must point to a dynamically allocated array or be null

typedef int arrT[42];
int *p = new arrT; // allocates an array of 42 ints; p points to the first one

delete [] p; // brackets are necessary because we allocated an array

(2). Smart Pointers and Dynamic Arrays

  • To use a unique_ptr to manage a dynamic array, we must include a pair of empty brackets after the object type
    • when destroys the pointer it manages, it will automatically use delete[].
  • When a unique_ptr points to an array, we cannot use the dot and arrow member access operators 因为unique_ptr points to an array, not an object .
  • when a unqiue_ptr points to an array, we can use the subscript operator to access the elements(return the object not array) in the array, 不能使用deference operator 去access 第一个元素
  • Unlike unique_ptr, shared_ptrs provide no direct support for managing a dynamic array. If we want to use a shared_ptr to manage a dynamic array, we must provide our own deleter
    • 如果没有提供 deleter, the code would be undefined: 跟我们使用动态数组, delete时忘记加[] 是一样的问题
  • no subscript operator for shared_ptrs, and the smart pointer types do not support pointer arithmetic. 因此访问数组中元素必须get 获取一个built-in pointer

unique_ptrs to Arrays

Syntax Description
unique_ptr<T[]> u u can point to a dynamically allocated array of type T
unique_ptr<T[]> u(p) u points to the dynamically allocated array to which the built-in pointer p pointers. p must be convertible to T*
u[i] Returns the object at position i in the array that u owns. u must point to an array

The brackets in the type specifier (<int[]>) say that up points not to an int but to an array of ints. 当销毁它的管理指针时候, 会自动call delete[]`

unique_ptr<int[]>up(new int[10]); //up 指向10个未初始化的int

up.release(); // automatically uses delete[] to destroy its pointer

for (size_t i = 0; i != 10; ++i)
    up[i] = i; // 为每个元素赋予一个新的值

为了使用shared_ptr 来管理动态数组, 必须提供一个动态数组

shared_ptr<int>sp(new int[10], [](int *p){delete[] p;});
sp.reset(); //uses the lambda we supplied that uses delete[] to free the array

shared_ptrs don’t have subscript operator and don’t support pointer arithmetic

for (size_t i = 0; i != 10; ++i)
    *(sp.get() + i) = i; // use get to get a built-in pointer

(f). Allocator Class

  • new combines allocating memory with constructing object(s) in that memory. Similarly, delete combines destruction with deallocation. Combining initialization with allocation is usually what we want when we allocate a single object.
    • 当分配一大块内存时, we often plan to construct objects in that memory as needed. we’d like to decouple memory allocation from object construction. means that we can allocate memory in large chunks and pay the overhead of constructing the objects only when we actually need to create them.

比如下面例子new 分配了 n 个string, 但是可能不需要n个string, 少量就满足了, 这样可能创建了一些永远得不到的对象. 而且每个元素被赋值两次, 第一次在default initialized, 第二次在assign 时候

string *const p = new string[n]; // construct n empty strings
  
string s;
string *q = p; //q指向p的第一个元素

while (cin >> s && q != p +n)
    *q++ = s; //赋予一个新值

const size_t size = q - p; //记住我们读取了几个string

delete[] p; // p 指向第一个数组, 记得用delete[] 来释放

Allocator

  • define in memory header, let user separate allocation from construction. It provides type-aware allocation of raw, unconstructed, memory.
  • define allocator, 需要specify type.
  • When an allocator object allocates memory, it allocates memory that is appropriately sized and aligned to hold objects of the given type:
  • It is an error to use raw memory in which an object has not been constructed
    • We must construct objects in order to use memory returned by allocate. Using unconstructed memory in other ways is undefined.
  • When we’re finished using the objects, we must destroy the elements we constructed, which we do by calling destroy on each constructed element
    • 只能 destroy elements that are actually constructed.
    • 当元素被销毁, , we can either reuse the memory to hold other object or return the memory to the system. We free the memory by calling deallocate
      • pass给deallocate的指针不能为空(null); it must point to memory allocated by allocate(分配内存的起点). Moreover, the size argument passed to deallocate must be the same size as used in the call to allocate that obtained the memory to which the pointer points. 如果size少了, 不会报错,会内存泄漏
Syntax Description
allocator<T> a Defines an allocator object named a that can allocate memory for objects of type T
a.allocate(n) Allocate raw, unconstructed memory to hold n objects of type T
a.deallocate(p, n) Deallocates memory that n objects of type T starting at the addresss in T* pointer p; p must be a pointer previously returned by allocate, and n must be the size requested when p was created. The user must run destory on any objects that were constructed in this memory before calling deallocated
a.construct(p, args) p must be a pointer to type T that points to raw memory; args are passed to constructor for type T, which is used to construct an object in the memory pointer to by p
a.destroy(p) Runs the destructor on object pointed to by the the T* pointer p, 需要一个一个删除

下面例子分配n个未初始化的string

allocator<string>alloc; // object that can allocate strings

auto const p = alloc.allocate(n); //分配n个unconstructed strings

The memory an allocator allocates is unconstructed. We use this memory by constructing objects in that memory. construct 接受0个或多个argument(must match constructor for that class) 用来initialize object. error use unconstructed memory; 用完对象后, 必须用destory 来销毁他们

auto q = p; // q will point to one past the last constructed element 

alloc.construct(q++); // *q is the empty string 

alloc.construct(q++, 10, 'c'); // *q is cccccccccc 

alloc.construct(q++, "hi"); // *q is hi!

cout << *p << endl; // ok: uses the string output operator

cout << *q << endl; // disaster: q points to unconstructed memory!


//必须删除用完的元素

while (q != p)
    alloc.destroy(--q); // free the strings we actually allocated


//free memory and return the memory to the system

alloc.deallocate(p, n);

Algorithms to Copy and Fill Uninitialized Memory

  • allocator的 construct只能一个一个construct, 不能批量construct, uninitialized_copyuninitialized_fill_n 可以批量 construct
  • The destination iterator passed to uninitialized_copy must denote unconstructed memory. Unlike copy, uninitialized_copy constructs elements in its destination.
  • Like copy, uninitialized_copy returns its (incremented) destination iterator. uninitialized_copy returns a pointer positioned one element past the last constructed element.
uninitialized_copy(b,e,b2) Copies elements from the input range denoted by iterators b and e into unconstructed, raw memory denoted by the iterator b2. The memory denoted by b2 must be large enough to hold a copy of the elements in the input range
uninitialized_copy_n(b,n,b2) Copies n elements starting from iterator b into raw memory starting at b2
uninitialized_fill(b,e,t) Constructs objects in the range of raw memory denoted by iterators b and e as a copy of t (从b到e 值均为t的拷贝)
uninitialized_fill_n(b,n,t) Constructs an unsigned number n objects starting at iterator b. b must denote unconstructed, raw memory large enough to hold the given number of objects

分配一个比vector中元素大一倍的动态内存(allocated memory), 将原来vector中元素拷贝到前一半空间, 对后一半空间给定值进行填充

// allocate twice as many elements as vi holds

auto p = alloc.allocate(vi.size() * 2);

// construct elements starting at p as copies of elements in vi

auto q = uninitialized_copy(vi.begin(), vi.end(), p);

uninitialized_fill_n(q, vi.size(), 42);  // initialize the remaining elements to 42

例子: 给一个文本, 给一个单词, 打印出这个单词在文本中出现多少回,在第几行,第几行的文本

  • 设计两个class TextQueryQueryResult
    • shared_ptr 用来传递 set (这个string 在第几行出现) 和 vector<string> (整个file 文本) 避免拷贝.
    • queryconst function: const function 巧妙使用, 因为不会改变class obejct
    • auto &lines = wm[word]; 用了 &. wm[word] 返回lvalue reference, 但是 auto 对reference deduce 是value, 为了keep reference 加上 &, 注意lines 是 shared_ptr, if(!lines) 判断shared_ptr是否为空
    • static shared_ptr<set<line_no>> nodata(new set<line_no>); 定义一个局部static 对象, 指向空的行号set 的shared_ptr,避免多次定义一样的nodata
    • for (auto num : *qr.lines) 解引用因为 qr.lines是shared_ptr, *(qr.file->begin() + num), qr.file也是shared_ptr
void runQueries(ifstream &infile){
    TextQuery tq(infile); /
    
    / iterate with the user: prompt for a word to find and print results
    while (true) {
        cout << "enter word to look for, or q to quit: "; string s;
        if (!(cin >> s) || s == "q") break;
        print(cout, tq.query(s)) << endl;
    }
}

class QueryResult; // 为了定义函数query 返回类型, 这个定义是必须的

class TextQuery {
    public:
        using line_no = std::vector<std::string>::size_type; 
        TextQuery(std::ifstream&);
        QueryResult query(const std::string&) const;
    private:
        std::shared_ptr<std::vector<std::string>> file; // input file
        
        // map of each word to the set of the lines in which that word appears 
        
        std::map<std::string,
            std::shared_ptr<std::set<line_no>>> wm;
};

TextQuery::TextQuery(ifstream &is): file(new vector<string>)
{
    string text;
    while (getline(is, text)) {
        file->push_back(text);  
        int n = file->size() - 1; //保存当前行号

        istringstream line(text);  //将文本分解为单词

        string word; 

        while (line >> word) {
            
            auto &lines = wm[word]; // lines is a shared_ptr

            if (!lines) // that pointer is null the first time we see word

                lines.reset(new set<line_no>); // allocate a new  set
                
            lines->insert(n); // insert this line number
        
        }
    }
}

class QueryResult {
    friend std::ostream& print(std::ostream&, const QueryResult&);
public:
    QueryResult(std::string s, std::shared_ptr<std::set<line_no>> p,
                std::shared_ptr<std::vector<std::string>> f):sought(s), lines(p), file(f) { }  
private: 
    std::string sought; //查询单词

    std::shared_ptr<std::set<line_no>> lines; //出现的行号

    std::shared_ptr<std::vector<std::string>> file; // input file 

};    


QueryResult TextQuery::query(const string &sought) const{
    //如果未找到sought, 将返回一个指向此set的指针

    static shared_ptr<set<line_no>> nodata(new set<line_no>); 
    //使用find 而不是下标运算 表示避免将单词添加到wm 中
    
    auto loc = wm.find(sought);
    if (loc == wm.end())
        return QueryResult(sought, nodata, file); // not found
    
    else
        return QueryResult(sought, loc->second, file);
}

string make_plural(size_t ctr, const string& word, const string& ending){
    return (ctr>1) ? word: word + ending;
}

ostream &print(ostream & os, const QueryResult &qr)
{
    os << qr.sought << " occurs " << qr.lines->size() << " "
        << make_plural(qr.lines->size(), "time", "s") << endl; 
    // print each line in which the word appeared
    
        for (auto num : *qr.lines) 
            // don't confound the user with text lines starting at 0

             os << "\t(line " << num + 1 << ") "
                 << *(qr.file->begin() + num) << endl;
    return os;
}




13. Copy Control

(a). Copy, Assign, and Destroy

  • A constructor is the copy constructor if its first parameter is a reference to the class type and any additional parameters have default values
    • copy constructor’s own parameter must be a reference: 否则调用永远不会成功, 因为一直自己copy 自己 parameter
    • first parameter 通常是 a reference to const
    • 通常copy constructor is not explicit
  • The compiler copies each nonstatic member from the given object into the one being created. The type of each member determines how that member is copied
    • class member 是class type, 则由copy constructor for that class 进行拷贝
    • 尽管array 不能copy, the synthesized copy constructor copies members of array by copying each element. 如果array elements 是class type, 用class的copy constructor.
  • copy initialization requires either the copy constructor or the move constructor.
    • Copy initialization 不仅在我们用=定义变量时发生, 在下面情况也发生:
      • insertpushcopy initialization, whereas emplacedirect initialization
      • 对于function call /return, 是 nonreference type are copy initialized</span>
      • Brace initialize the elements in an array or the members of an aggregate class
    • 对于有explicit 限定的constructor, 不能copy initialization. 只能direct initialization 比如:vector<int>(10);, 但不可以 vector<int>v = 10;
    • During copy initialization(implicit type conversion), the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly. That is, the compiler is permitted to rewrite as direct initialization
      • 即使忽略了copy/move constructor, 但是copy/move constructor 必须exist(自己定义or compiler generated) and must be accessible (not private) in program
      • 下面例子很重要,

Synthesized Copy constructor 会copy array element

struct test{
    test(const test& t){this->a = t.a;
        cout <<"in "<<t.a<<endl;;
    }
    test(int num){
        a = num;
    }
    int a;
};

class Ani{
public:
    test array[2];
    Ani(): array{test(10),test(15)}{}
};

Ani a;
Ani b = a; 
//print in 10

//print in 15 

string dots(10, '.'); // direct initialization 

string s(dots);  // direct initialization 

string s2 = dots;  // copy initialization 

string null_book = "9-999-99999-9"; // copy initialization 

string nines = string(100, '9'); // copy initialization

Constraints on Copy Initialization: library constructor is explicit

vector<int> v1(10); // ok: direct initialization

vector<int> v2 = 10; // error: constructor that takes a size is explicit

void f(vector<int>); // f's parameter is copy initialized

f(10); // error: can't use an explicit constructor to copy an argument

f(vector<int>(10)); // ok: directly construct a temporary vector from an int

The Compiler Can Bypass the Copy Constructor

string null_book = "9-999-99999-9"; // copy initialization into

string null_book("9-999-99999-9"); // compiler omits the copy constructor

  • 例子一, implicit type conversion, 因为constructor not specify explicit
  • 例子二: 因为copy constructor 是private(inaccessible), 不能copy
//case 1

struct str{
    str(const str& l){
        cout <<" goin "<<l.a<<endl;
        this->a = l.a;
    }
    str(string a){
        this->a = a;
    }
    string a;
};

string dog = "dog";
str a =dog; //call constructor

str b = a; //call copy constructor

//case 2

struct str{
    str(string a){
        this->a = a;
    }
    string a;
private:
    str(const str& l) = default
};
str b = "dog";
str a = b;//error cannot copy 

(2).The Copy-Assignment Operator

  • To be consistent with assignment for the built-in types, assignment operators usually return a reference to their left-hand operand. the library generally requires that container have assignment operators that return as reference
  • assigns each nonstatic member of the right-hand object to to left-hand object

(3). Destructor

  • destructors: free the resources used by an object and destroy the nonstatic data members of the object
  • takes no parameters, it cannot be overloaded.
  • destruction 顺序, the destructor function body is executed first and then the members are destroyed( call member 的destructor). Members are destroyed in reverse order from the order of their initialization
  • Members of class type are destroyed by running the member’s own destructor. The built-in types(包括了smart pointer) do not have destructors, so nothing is done to destroy members of built-in type
  • The destructor is not run when a reference or a pointer to an object goes out of scope.
  • 如果destructor 不是用来阻止对象被摧毁, the synthesized destructor has an empty function body.

(4). The Rule of Three/Five

记: 1. constructor 2. copy constructor 3. copy assignment operator 4. destructor 5. move constructor 6 move assignment operator

  • destructor 只有 当base destructor 是private 时候才不会生成
  • constructor synthesize only if no constructor defined by user (注: copy / move constructor 也算 constructor )
  • copy constructor: no 3 ,4 , 5, 6 defined 且member 是可以copy的, ( member 是class type 的 copy constructor accessible)
  • copy assignment operator: no 2, 4, 5, 6 defined, 且member 没有const or reference, member 是可以copy assignment的, ( member 是class type 的 copy assignment operator accessible) .
  • move constructor: no 2, 3, 4, 6 defined 且member 没有const or reference member 是可以move的, ( member 是class type 的 move constructor accessible)
  • move assignment operator: no 2, 3, 4, 5 defined 且member 是可以move assigned 的, ( member 是class type 的 move assigment operator accessible)
  • copy / move only for nonstatic data member
  • 即使声明synthesized as =delete 也算user defined

总结:

  • 2,3,5,6, 如果任何一个定义了, compiler都不会synthesize其他的,
  • 如果定义了 destructor, 2,3,5,6 也都不会生成
  • 注上面accessible 定义是 nondeleted and inaccessible
  • If the class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well.
  • 一般需要copy constructor时候, 也需要copy assignment operator, 反之亦然.
  • When we specify = default on the declaration of the member inside the class body, the synthesized function is implicitly inline. If do not want inline , 也可以 specify = default on the member’s definition outside class
    • We can use = default only on member functions that have a synthesized version (i.e., the default constructor or a copy-control member).
  • Preventing Copies 对于不能copy 的class i.e IO class: 以免多个对象写入或者读取相同的IO缓冲
    1. Defining a Function as Deleted:
      • Unlike = default, = delete must appear on the first declaration of a deleted function, 函数第一次声明的时候就要加=delete, 不能像= default, 还可以定义在外部
      • Unlike = default(只能用于compiler synthesized function), we can specify = delete on any function
      • we did not delete the destructor: 因为If the destructor is deleted, then there is no way to destroy objects of that type. 同样 不能create objects has a deleted destructor。 只能create dynamically allocated object and store in pointer
        • Not possible to define an object or delete a pointer to a dynamically allocated object of a type with a deleted destructor(因为无法删除).
    2. private Copy Control:
      • copy constructor and copy-assignment operator are private, user 不能 copy such objects. However, friends and members of the class can still make copies
        • 阻止friends and members of class still make copies, 我们declare these members as private but do not define them.
      • User code 尝试copy flagged as error at compile time, compies made in member functions or friends(声明但不定义) will result in an error at link time

如果只定义了destructor, 而没有定义copy constructor 和 copy-assignmnet operator 是错误的. 比如下面例子, 使用了synthesized versions of the copy constructor and copy-assignment operator,造成错误是多个HasPtr 指向相同的内存, 比如rethp, 两个对象包含same pointer value. The code will delete pointer twice, which is an error

class HasPtr { 
public:
    HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) { }
    ~HasPtr() { delete ps; }
    // WRONG: HasPtr needs a copy constructor and copy-assignment operator 

};

HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied
{
    HasPtr ret = hp; // copies the given HasPtr

    return ret; // ret and hp are destroyed

}

HasPtr p("some values");
f(p); // when f completes, the memory to which p.ps points is freed 

// 因为pass by value, value is deleted after function completed

HasPtr q(p); // now both p and q point to invalid memory!

在function 内部声明 =default 是inline, 如果在function 外面声明=default 不是inline

class Sales_data { 
public:
    // copy control; use defaults 
    
    Sales_data() = default;
    Sales_data(const Sales_data&) = default; 
    Sales_data& operator=(const Sales_data &); 
    ~Sales_data() = default;
    // other members as before
    
};

Sales_data& Sales_data::operator=(const Sales_data&) = default;

阻止拷贝:

struct NoCopy {
    NoCopy() = default; // use the synthesized default constructor 

    NoCopy(const NoCopy&) = delete; // no copy
    
    NoCopy &operator=(const NoCopy&) = delete; // no assignment 
    
    ~NoCopy() = default; // use the synthesized destructor

};

destructor 是 deleted,

struct NoDtor {
    NoDtor() = default; // use the synthesized default constructor

    ~NoDtor() = delete; // we can't destroy objects of type NoDtor 

};
NoDtor nd; // error: NoDtor destructor is deleted

NoDtor *p = new NoDtor(); // ok: but we can't delete p 

delete p; // error: NoDtor destructor is deleted

The Copy-Control Members May Be Synthesized as Deleted (deleted or inacessble : DI):

  • The synthesized destructor is defined as deleted if data member 的 class 的destructor is DI
  • The synthesized copy constructor is defined as deleted if 有class member的copy constructor DI or class 有member 有DI destructor, 如果有const, reference 可以copy-construct, 因为与copy的对象, reference 与其shared 一个对象, const 也会生成在constructor, 但是必须提供non-default constructor
  • The synthesized copy-assignment operator is defined as deleted: 如果member 是 DI copy-assignment operator, or class has const or reference member
  • The synthesized default constructor is defined as deleted if member has DI destructor(创建的对象无法删除, 不可以的), or reference member 没有in-class initializer, or const member 的type 没有explicitly define a default constructor(比如自己定义的类) and 没有in class initializer

总结上面的:

  • 揭示道理是: 如果class有member 不能default constructed, copied, assigned, or destoryed 则class对应的function也是deleted
  • 如果有const成员, 不能改变值, reference 成员不能改变绑定对象, 则不能使用synthesized copy-assignment operator.
  • 本质上, 如果有member 不能copy, assign, destory, 则class 也不会生成相应copy-control members

下面例子copy constructed ok的, 但是要提供non-default constructor

class Ani{
public:
    Ani(int a_): a(a_){}
    int &a;
};

Ani a(5);
Ani b(a);

(b). Copy Control and Resource Management

  • copy like a value: copy and original 是独立的,改变一个不会影响另一个
  • copy like a pointer: copy and original use the same underlying data. 改变一个影响另一个

(1). Classes That Act Like Values

下面例子

  • dynamically allocates its own copy of that string and stores a pointer to that string in ps.
  • Assignment operators typically combine the actions of the destructor and the copy constructor. 顺序很重要
    1. 像destructor, 先destroys the left-hand operand’s resouces
    2. 像copy constructor, assignment copies data from the right-hand operand.
  • Moreover, when possible, we should also write our assignment operators so that 让left-hand operands 是有意义(sensible)的状态当发生异常时
class HasPtr { 
public:
    HasPtr(const std::string &s = std::string()): 
        ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr &p):
        ps(new std::string(*p.ps)), i(p.i) { } 
    
    HasPtr& operator=(const HasPtr &);
    ~HasPtr() { delete ps; }
private:
    std::string *ps;
    int i;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    auto newp = new string(*rhs.ps); // copy the underlying string
    
    delete ps;  // free the old memory

    ps = newp; // copy data from rhs into this object

    i = rhs.i;  // return this object
    
    return *this;
}

Key Concept: Assignment Operators

  • Assignment operators must work correctly if an object is assigned to itself.
  • Most assignment operators share work with the destructor and copy constructor
  • copy assignment 最后返回reference, 否则如果return value, 需要call copy constructor 还有call destructor to destory temporary value
  • Good Pattern:
    1. copy right-hand operand into a local temporary.
    2. step1 完成后, safe to destory existing member of left-hand operand.
    3. copy the date from temporary into members of left-hand operand.

bad example, 如果是自己拷贝到自己, 把underlying object就给删除了, 最后访问一个无效的指针, What happens is undefined.

HasPtr&
HasPtr::operator=(const HasPtr &rhs)
{
    delete ps; // frees the string to which this object points

    // if rhs and *this are the same object, we're copying from deleted memory!

    ps = new string(*(rhs.ps)); 
    i = rhs.i;
    return *this;
}

(1). Classes That Act Like Pointers

  • 需要copy constructor and copy- assignment operator to copy the pointer member,而不是copy pointer 指的值
  • 需要free memory when 最后一个指向的对象被销毁时
  • Easiest way is use shared_ptr to manage the resource(会reference count, track有多少个uses sharing th pointed-to object)
  • 如果自己创建reference count, 问题是where to put reference count, 比如下面例子, if store in object的话, 也许不能update correctly. 解决方法是store the counter in dynamic memory(不能是static, 比如 HasPtr aHasPtr b 要不是copy constructor, 计数器不同的 )
    • When we copy or assign an object, we’ll copy the pointer to the counter. That way the copy and the original will point to the same counter.

当把p1 给 p3, 递增p1的计数器, 但是却没法递增p2的计数器

HasPtr p1("Hiya!");
HasPtr p2(p1); // p1 and p2 point to the same string 

HasPtr p3(p1); // p1, p2, and p3 all point to the same string

E.g. Reference counting works as follows:

  1. 每一个constructor (copy constructor 除外) creates a counter.记录多少个objects share state with the object we are creating. When we create an object, initialize the counter as 1
  2. copy constructor not allocate a new counter; copies the data members of its given object, including the counter 并 递增reference counter
  3. destructor 递减counter, 如果count 变成0, destructor deletes that state
  4. copy assignment 递减左侧对象的counter, 递增右侧对象的counter, 如果left-hand operand goes to zero. destory the state of left-hand operand. 通过先增加右侧, 再减少左侧的counter, 这样子保证不会先在左侧reference count等于0时 free memory

e.g. reference count. 注意

  • destructor 不能随便删除, 只有counter go to zero, then free the memory of both ps and use point.
  • copy assignment operator 增加右侧运算对象计数器, 减少左侧对象计数器, 如果左侧计数器等于0,delete/free memory. 而且要能handle self-assignment.

下面是很好的例子, 注意什么时候删除pointer

class HasPtr { 
public:
    // constructor allocates a new string and a new counter, which it sets to 1 
    
    HasPtr(const std::string &s = std::string()):
        ps(new std::string(s)), i(0), use(new std::size_t(1)) {}

    // copy constructor copies all three data members and 递增计数器
    
    HasPtr(const HasPtr &p):
        ps(p.ps), i(p.i), use(p.use) { ++*use; } 
    
    HasPtr& operator=(const HasPtr&);
    ~HasPtr();
private:
    std::string *ps;
    int i; 
    std::size_t *use;//reference counter
     
    // member to keep track of how many objects share *ps

};

//destructor 

HasPtr::~HasPtr()
{ 
    if (--*use == 0) { 
        //delete string and reference counter

        delete ps;  
        delete use; 
    }
}

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    ++*rhs.use; // increment the use count of the right-hand operand

    if (--*use == 0) { // then decrement this object's counter
    
        // free this object's allocated members

        delete ps; 
        delete use;
    }
    ps = rhs.ps; 
    i = rhs.i; 
    use = rhs.use; 
    return *this;
}

(c). Swap

  • swap is important, 对于algorithm that reorder elements. Such algorithms call swap whenever they need to exchange two elements.
    • If a class 定义了自己 swap, then the algorithm uses that class-specific version. Otherwise, use swap defined by the library.
  • In principle, none of this memory allocation is necessary for pointer.

swap 通常involves a copy(copy constructor) and two assignment(assignment operator).

HasPtr temp = v1; // copy constructor

//assignment operator

v1 v1 = v2; 
v2 = temp; 

//pointer 

string *temp = v1.ps; // copy constructor

//assignment operator

v1.ps = v2.ps;
v2.ps = temp;

(1).Write own swap function

  • override the default behavior of swap by defining a version of swap that operates on our class.
  • declaring swap as a friend to give it access to HasPtr’s (private) data members.
  • optimize our code, we’ve defined swap as an inline function
  • Unlike the copy-control members, swap is never necessary. However, defining swap can be an important optimization for classes that allocate resources.
  • if a class has a member that has its own type-specific swap function, calling std::swap would be a mistake.
    • 下面例子有正确的使用方法: type-specific version of swap 是better match than std. 因此当没有type-specific version of swap, then—assuming there is a using declaration for swap in scope—calls to swap will use the version in std.
class HasPtr {
    friend void swap(HasPtr&, HasPtr&);
    // other members as before

};

inline void swap(HasPtr &lhs, HasPtr &rhs) {
    using std::swap;
    swap(lhs.ps, rhs.ps); // swap the pointers, not the string data 
    
    swap(lhs.i, rhs.i); // call std::swap, 因为built-in 么有type-specific version
    
}

如果有自己member 的swap, 用std::swap是mistake(不会报错). 自己member swap 通常是class friend, argument-dependent lookup

void swap(Foo &lhs, Foo &rhs)
{
    // WRONG: this function uses the library version of swap, not the HasPtr version

    std::swap(lhs.h, rhs.h);
    // swap other members of type Foo
    
}

//正确方式

void swap(Foo &lhs, Foo &rhs)
{
    using std::swap;//表示只有当没有type-specific swap时候,才会用std::swap的

    std::swap(lhs.h, rhs.h); //uses the HasPtr version of swap

    // swap other members of type Foo
    
}

(2).Using swap in Assignment Operators

  • Classes that define swap often use swap to define their assignment operator. These operators use a technique known as copy and swap. 把右侧对象swap给左侧
  • 注意右侧对象是pass by value which means copy constructor
  • Assignment operators that use copy and swap are automatically exception safe and correctly handle self-assignment.

例子中

  • the parameter is not a reference, pass right-hand operand by value.
  • 当assignment operator 完成后, rhs is destroyed and the HasPtr destructor is run
  • 是自动handle self-assignment and exception safe.
    • copy right-hand operand before changing left-hand operand, it handles self assignment in the same was as we did in our original copy constructor
    • 唯一可能报错是copy constructor, 如果有exception occurs,it will happen before we change left-hand operand.
// note rhs is passed by value, which means the HasPtr copy constructor 

// copies the string in the right-hand operand into rhs

HasPtr& HasPtr::operator=(HasPtr rhs)
{
    // 交换左侧对象和local varaible rhs

    swap(*this, rhs); // rhs now points to the memory this object had used
    
    return *this; // rhs is destroyed, which deletes the pointer in rhs

}

(d). Classes That Manage Dynamic Memory

  • Some classes need to allocate a varying amount of storage at run time

StrVec Class Design:类似于vector, add elements时候看有没有足够space, 有的话直接construct an object in next available sport, 没有的话, vector is reallocated(obtains new space, moves the existing elements into that space, frees the old space, and adds the new element)

  • use an allocator to obtain raw memory
  • 因为allocator 获得内存时raw/unconstructed memory, use allocator’s construct member to create objects. 当remove 时候用 allocatordestory
  • 每一个StrVec 有三个pointer
    • elements, points to first element in the allocated memory
    • first_free, points just after the last actual element
    • cap, points just past the end of the allocated memory
  • 还有个member named allocator<string> alloc . alloc will allocate the memory used by a StrVec.
  • 有四个ultility functions:
    1. alloc_n_copy will allocate space and copy a given range of elements.
    2. free will destroy the constructed elements and deallocate the space.
    3. chk_n_alloc will ensure that there is room to add at least one more element to StrVec,如果没有足够空间, call reallocate
    4. reallocate will reallocate the StrVec when it runs out of space.

下面code

  • allocator allocate是raw/unconstructed memory, 必须call construct 去construct an object in that memory, construct 会用到first_free 并递增first_free(递增得到next, unconstructed element)
  • uninitialized_copy 返回时一个指针 指向one element past the last constructed element
  • allocate 返回 start of memory block
  • Moving, Not Copying, Elements during Reallocation. function 应该做:
    1. Allocate memory for a new, larger array of strings
    2. Construct the first part of that space to hold the existing elements by move
    3. Destroy the elements in the existing memory and deallocate that memory

copy string 是value like, 改变一个不会影响另一个. 所以copy a string肯定会allocate memory for those characters and destory a string 肯定会free memory used by string. Perfomance will be much better by avoid overhead of allocating and deallocating string 当reallocate

Move Constructor and std::move

  • 有一些class(包括string) 都定义了 move constructor. 可以想象每个string 都有一个a pointer to an char array. string的move constructor copies pointer 而不是 allocating space for and copying the chars
  • 是将resource move(而不是拷贝) from given object to 正在创建的对象(take ownership). 用于 reallocate 时候
  • move constructor 保证移后源(moved-from) 仍然保持一个valid, destructible state.
  • move 定义在 utility header, 使用move 不需要provide a using declaration (将在18.2.3中讲到), 使用move 直接call的是 std::movenot move
  • 下面code中的reallocatemove完成之后, 不知道旧的StrVec内存中 string 是什么值, 但保证对他们执行string destructor is safe
class StrVec{
public:
    StrVec(): // the allocator member is default initialized 

        elements(nullptr), first_free(nullptr), cap(nullptr) {}
    StrVec(const StrVec&); 
    StrVec &operator=(const StrVec&);
    ~StrVec(); 
    void push_back(const std::string&); 
    size_t size() const { return first_free - elements; } 
    size_t capacity() const { return cap - elements; } 
    std::string *begin() const { return elements; } 
    std::string *end() const { return first_free; }

private:
    std::allocator<std::string> alloc; // allocates the elements 
    
    // used by the functions that add elements to the StrVec

    void chk_n_alloc()
    { if (size() == capacity()) reallocate(); }
    
    //used by the copy constructor, assignment operator, and destructor 

    std::pair<std::string*, std::string*> alloc_n_copy
            (const std::string*, const std::string*);
    void free();
    void reallocate();
    std::string *elements;//指向数组的首元素

    std::string *first_free;//指向数组第一个空闲元素指针

    std::string *cap; //指向数组尾后指针

};

void StrVec::push_back(const string& s){
    chk_n_alloc(); // ensure that there is room for another element 

    // construct a copy of s in the element to which first_free points 
    
    alloc.construct(first_free++, s);
}

std::pair<std::string*, std::string*> alloc_n_copy
            (const std::string* b, const std::string* e){
    auto data = alloc.allocate(e-b);            
    //返回the start of the allocated memory and value return from uninitialized_copy 

    //uninitialized_copy 返回时一个指针 指向one element past the last constructed element

    return {data, uninitialized_copy(b,e,data)};
}

void StrVec::free(){
    //不能传递一个空指针 
    
    if (elements) {
        // destroy the old elements in reverse order
    
        for (auto p = first_free; p != elements; /* empty */)
            alloc.destroy(--p); 
        }
        alloc.deallocate(elements, cap - elements);
    }
}

StrVec::StrVec(const StrVec &s)
{
    // call alloc_n_copy to allocate exactly as many elements as in s
    
    auto newdata = alloc_n_copy(s.begin(), s.end()); 
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::~StrVec() { free(); }

StrVec &StrVec::operator=(const StrVec &rhs)
{
    // call alloc_n_copy to allocate exactly as many elements as in rhs
    
    auto data = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::reallocate(){
    //将分配当前大小两倍的内存空间

    auto newcapacity = size() ? 2 * size() : 1;
    //分配新内存

    auto newdata = alloc.allocate(newcapacity);

    auto dest = newdata; // 返回the new start of the allocated memory

    auto elem = elements; //points to the next element in old array 

    for(size_t i = 0; i!=size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
        //will take over ownership of the memory from the string to which elem points.


    free(); //一旦移动完元素就释放旧内存空间

    // We don’t know what value the strings in the old StrVec memory have,
    
    // but we are guaranteed that it is safe to run the string destructor on these objects.

    //更新数据结构

    elements = newdata; 
    first_free = dest;
    cap = elements + newcapacity;
}

(e). Rvalue References

  • An rvalue reference is a reference that must be bound to an rvalue(an object that is about to be destroyed). (除了template, auto)
    • 因此可以自由将rvalue reference 移动到另一个对象中
  • an lvalue expression refers to an object’s identity whereas an rvalue expression refers to an object’s value
  • Lvalues Persist; Rvalues Are Ephemeral
    • Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions.
    • All variables Are Lvalues
  • return lvalues 表达式的例子: along with the assignment, subscript, dereference, and prefix increment/decrement operators
  • Functions that return a nonreference type, along with the arithmetic, relational(true/false), bitwise, and postfix increment/decrement operators, all yield rvalues

总结:

  • rvalue-reference: 只能绑定到 rvalue, rvalue reference
    • r-value reference <– rvalue✔️:
      • 可以绑定到literals, or expression return rvalue. but a lvalue reference cannot bind these
    • r-value reference <– rvalue reference,
      • r-value reference <– a variable which type is rvalue reference . Error: 因为all varaibles are lvalue
      • r-value reference <– move()``` ✔️: can bind to rvalue reference from move
    • r-value reference <– lvalue, Error
    • r-value reference <– lvalue reference, Error
  • lvalue:
    • lvalue <– rvalue✔️:
    • lvalue <– rvalue reference✔️:
    • lvalue <– lvalue✔️:
    • lvalue <– lvalue reference✔️
  • lvalue reference: 要绑定到object 上, 只能绑定 lvalue, lvalue reference
    • lvalue <– rvalue Error
      • but const lvalue reference can take rvalue
    • lvalue <– rvalue reference
      • lvalue <– a variable with type is rvalue reference✔️因为rvalue reference 表达式是 lvalue
      • lvalue <– move() return rvalue reference Error
    • lvalue <– lvalue✔️:
    • lvalue <– lvalue reference✔️

例子

//rvalue reference:

int lval = 10;
int & lval_ref = lval; 

int && rval_ref0 = 10; //error: cannot bind a lvalue 

int && rval_ref1 = lval_ref; //error: cannot bind a lvalue reference

int && rval_ref2 = lval * 10; //okay: can bind rvalue reference 

int &&rr1 = 42; // ok: literals are rvalues

int && rval_ref3 = rval_ref2; //error: cannot bind a varabile with type rvalue reference variable

//because rvalue reference variable is lvalue 

int && rval_ref4 = move(rval_ref2); //okay: bind a rvalue reference  


//lvalue:

int lval = 10, &lval_ref = lval;
int && rval_ref = lval * 10;

int lval_0 = lval*10; //okay: can bind to a rvalue 

int lval_1 = rval_ref; //okay: can bind to a rvalue reference

int lval_2 = lval; //okay: can bind to a lvalue 

int lval_3 = lval; //okay: can bind to a lvalue reference


//lvalue reference: 

int lval = 10, &lval_ref = lval;
int && rval_ref = lval * 10;

int& lval_ref1 = lval*10; //error: cannot bind to a rvalue 

int lval_ref1 = rval_ref; //okay: can bind to a rvalue reference. the expression rr1 is an lvalue!

int lval_ref2 = lval; //okay: can bind to a lvalue 

int lval_ref3 = lval; //okay: can bind to a lvalue reference


const int &lval_ref4= lval*10;; // ok: we can bind a reference to const to an rvalue

The Library move Function

  • 尽管不能直接把rvalue reference 绑定到 lvalue, 但可以 cast lvalue to rvalue type by use std`::move
  • move 告诉compiler 我们有个lvalue, 但想让对待rvalue 一样处理它.
  • We can destroy a moved-from object and can assign a new value to it, but we cannot use the value of a moved-from object.
  • 不用using declaraction for move, 因为我们调用 std::move 而不是 move, 原因将在18.2.3中讲到(因为argument dependent lookup)
int &&rr1 = 42; 

int &&rr3 = std::move(rr1);


  • moving, rather than copying, the object can provide a significant performance boost
  • A second reason to move rather than copy occurs in classes such as the IO or unique_ptr classes. 这些class resources 不能shared. 因此objects of these types 不能被copies 但可以move

(f). Move Constructor and Move Assignment

  1. Move Constructor
  2. Move Assignment
  3. Synthesized Move Operations
  4. Move Iterators
  5. Rvalue References and Member Functions
  6. Rvalue and Lvalue Reference Member Functions

(1). Move Constructor

  • move constructor: the reference parameter is an rvalue reference, 像copy constructor, any additional parameters must all have default arguments.
  • move constructor must ensure that destroying moved- from object(移后源) will be harmless . original object must no longer point to those moved resources. responsibiles 转移给了newly created object
  • 因为move operation 不allocate any resources, 所以通常不会throw exception. 如果不告诉compiler 它不会throw exception, compiler也许会做extra work to cater to the possibility that也许throw
    • specify noexcept on move constructor, 是promise that a function does not throw any exceptions. specify noexcept on a function after its parameter list. 在parameter list 内在constructor initializer list前面
    • 必须specify noexcept both on declaration in class headerdefinition outside class
    • Move constructors and move assignment operators that cannot throw exceptions should be marked as noexcept.
  • move operation 不throw exception 基于两个事实:
    1. 虽然move operations 不抛出异常,但抛出异常是被允许的
    2. library containers 自身提供保障当异常发生, 比如vector push_back 有exception, vector itself 是unchanged

noexcept

class StrVec { 
public:
    StrVec(StrVec&&) noexcept; //move constructor

};

StrVec::StrVec(StrVec &&s) noexcept : /* member initializers */
 { /* constructor body */ }

例子中move constructor 没有allocate new memory. take over memory in given StrVec, 之后moved-fom object 会被destoryed by calling destructor (会deallocate. 如果忘记 s.first_free = nullptr, 会deleted memory we just moved)

StrVec::StrVec(StrVec &&s) noexcept // move won't throw any exceptions

// member initializers take over the resources in s

: elements(s.elements), first_free(s.first_free),
{
    // leave s in a state in which it is safe to run the destructor

    s.elements = s.first_free = s.cap = nullptr;
}

可能出现的问题: 比如在reallocate(move elements from old space to new memory) 过程中发生异常. If reallocation 使用move constructor throw exception 在移动了部分而不是全部元素后。问题就产生了: 旧空间中的移动元素已经改变,而新空间中的unconsturcted elements 可能尚不存在, vector满足不了 当有exception 自身unchanged 性质. 所以这种情况下, vector 会使用copy constructor: 如果exception 发生, elements are construct in new memory, old elements remain unchanaged. 如果exception 发生, vector free the space it allocated and return. 原始的vector elements still exist. 为了避免这种潜在问题, vector使用move constructor 而不是copy constructor 除非知道move constructor cannot throw an excetpion. tell compiler safe to use during reallocation. 必须explicitly tell library that our move constructor safe to use by marking noexcept

(2). Move Assignment

  • 像move constructor, if move-assignment operator won’t throw any exceptions, we should make it noexcept. Like a copy-assignment operator, a move-assignment operator must guard against self-assignment
  • As in any other assignment operator, it is crucial that we 在使用右侧运算对象之前,不能释放左侧运算对象的资源 (可能是相同资源).
  • After a move operation, “moved-from” object must remain a valid, destructible object(可以run destructor) but no assumptions about its value.
    • valid 表示可以依然为其赋予新值, 可以安全使用不依赖其结果,比如string当移后, remain valid, 虽然可以run operations such as empty or size on moved-from objects. 但不知道会得什么结果.

例子中 we do check self-assignment. 因为callingmove 可以让rvalue 作为右侧运算符,

StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
    // 直接检测self-assignment

    if (this != &rhs) {
        free(); // free existing elements 
        
        elements = rhs.elements; // take over resources from rhs
        
        first_free = rhs.first_free; 
        cap = rhs.cap;
        // leave rhs in a destructible state

        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    } 
    return *this;
}

(3). Synthesized Move Operations

  • when a class not move operation(也没有synthesized的), the corresponding copy operation is used in place of move through normal function matching

  • move operation is never implicitly defined as a deleted function. 但是如果我们explicit ask compiler to gerenerate a move operation by using =default, compiler unable to move member, then move operation 被定义成 deleted. (比如class member unable synthesize move operation)

// the compiler will synthesize the move operations for X and hasX 

struct X {
    int i; // built-in types can be moved s
    
    td::string s; // string defines its own move operations

};
struct hasX {
    X mem; // X has synthesized move operations 

};

X x, x2 = std::move(x); // uses the synthesized move constructor

hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor

比如下面例子, 定义了copy constructor 没有定义move constructor, 因为忽略了move contructor的declaration, compiler 不能generate move constructor; 如果member class 都可以copy, 会用copy constructor.

// assume Y is a class that defines its own copy constructor but not a move constructor

struct hasY {
    hasY() = default;
    hasY(hasY&) = default;
    Y mem; // hasY will have a deleted move constructor

};
hasY hy, hy2 = std::move(hy); // error: move constructor is deleted

//error 原因是 hasY & lvalue reference, 不接受rvalue

struct hasY {
    hasY() = default;
    hasY(const hasY& y) {
 	cout << "int copy constructor" <<endl;
    }
 };
 hasY hy, hy2 = std::move(hy);//ok, print: in copy constructor
 
 //因为const reference 接受rvalue 
 
  • When a class has both a move constructor and a copy constructor, the compiler uses ordinary function matching to determine which constructor to use
    • rvalue will call move constructor, 因为move constructor is better match 不用conversion

第一个例子, v2是lvalue, 不能pass to move assignment(rvalue reference). 第二个例子, getVec 返回时rvalue, 赋值是move-assignment operator. assign rvalue (实际上viable for both move-assignment and copy assignment. 但是calling copy assignment 需要conversion to const, whereas StrVec&& is exact match )

StrVec v1, v2;
v1 = v2; // v2 is an lvalue; copy assignment

StrVec getVec(istream &); // function getVec returns an rvalue

v2 = getVec(cin); // getVec(cin) is an rvalue; move assignment

下面例子call copy constructor, 因为没有move constructor available

class Foo {
public:
    Foo() = default;
    Foo(const Foo&); // copy constructor

// other members, but Foo does not define a move constructor

};
Foo x;
Foo y(x); //copy constructor; x is an lvalue 

Foo z(std::move(x)); //copy constructor, 因为没有available move constructor 

//can convert Foo&& to const Foo&

  • Move constructor 设置pointer to zero, to ensure that it is safe to destory moved-from object. function 不会throw exception, 因此mark as noexcept
  • Assignment operator 的parameter has a nonreference parameter, which means the parameter is copy initialized: lvalue are copies and rvalue are moved. 因此 single assignment operator acts as both the copy-assignment and move- assignment operator
    • Regardless of whether the copy or move constructor was used, the body of the assignment operator swaps the state of the two operands.
class HasPtr { 
public:
    // added move constructor
    
    HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}

    // assignment operator is both the move- and copy-assignment operator 

    HasPtr& operator=(HasPtr rhs)
        { swap(*this, rhs); return *this; } 
};

hp = hp2; // hp2 is an lvalue; copy constructor used to copy 

hp2 hp = std::move(hp2); // move constructor moves hp2

rvalue reference call pointer &, 因为variable is lvalue

Message::Message(Message &&m): contents(std::move(m.contents)) 
//move constructor 

{
    move_Folders(&m); // moves folders and updates the Folder pointers
    
}

Message& Message::operator=(Message &&rhs)
{
    if (this != &rhs) { // direct check for self-assignment

        remove_from_Folders(); //用于销毁左侧运算对象的旧状态

        contents = std::move(rhs.contents); // move assignment

        move_Folders(&rhs); // reset the Folders to point to this Message

    } 
    return *this;
}

(4). Move Iterators

  • Ordinarily, an iterator dereference operator returns an lvalue reference to the element. Unlike other iterators, the dereference operator of a move iterator yields an rvalue reference.
  • transform an ordinary iterator to a move iterator by calling the library make_move_iterator function
  • library makes no guarantees about which algorithms can be used with move iterators and which cannot. 只有确信当在算法运行完 不再访问它时(在用它为其他的赋值, 或者pass给user的function), 才能pass move iterator
  • 如果小心使用move 可以大幅度提高性能, 如果随意在普通用户代码中使用move, 很可能导致难以查找的错误, 而难以提高性能

下面例子用到 move iteartors, dereference operator yields an rvalue reference, construct will use the move constructor to construct the elements

void StrVec::reallocate()
{
    // allocate space for twice as many elements as the current size

    auto newcapacity = size() ? 2 * size() : 1;
     auto first = alloc.allocate(newcapacity);
    // move the elements 
    
    auto last = uninitialized_copy(make_move_iterator(begin()), 
        make_move_iterator(end()), first); 
    free();  // free the old space

    elements = first; // update the pointers 
    
    first_free = last;
    cap = elements + newcapacity;
}

(5). Rvalue References and Member Functions

  • 如果一个函数同时提供了copy and move 两个版本,可以获益
    • 一个版本是 take an lvalue reference to const T&
    • 另一个版本是 takes an rvalue reference to nonconst T && and will be run when we pass a modifiable rvalue. This version is free to steal resources from its parameter
  • 一般来说,不用为function 定义 const X&& or a plain X&. 通常we pass an rvalue reference when want to steal from argument. the argument must not be const. 类似的, 拷贝对象不应该改变被copied的对象, no need to define a version that take a plain X& parameter.

比如 the library containers that define push_back provide two versions:

  • 版本一: can pass any object that can be converted to tye X
  • 版本二: pass only an rvalue that is not const
void push_back(const X&); // copy: binds to any kind of X

void push_back(X&&); // move: binds only to modifiable rvalues of type X

两个版本的push_back 区别在于 rvalue reference version 的 push_back call move to pass parameter to construct, construct 通过第二个和第二个之后的参数来确定用哪个constructor, 因为move returns an rvalue reference, type of argument to construct is string&&. 因此string 的 move constructor will be used to construct a new last element


void StrVec::push_back(const string& s)
{
    chk_n_alloc(); // ensure that there is room for another element

    // construct a copy of s in the element to which first_free points
    
    alloc.construct(first_free++, s);
    } 
void StrVec::push_back(string &&s)
{
    chk_n_alloc(); // reallocates the StrVec if necessary
    
    alloc.construct(first_free++, std::move(s));
}


StrVec vec; 
string s = "some string or another";
vec.push_back(s); // calls push_back(const string&) 

vec.push_back("done"); // calls push_back(string&&)

(6).Rvalue and Lvalue Reference Member Functions

  • can call a member function on an object, regardless of whether that object is an lvalue or an rvalue
  • 还可以对rvalue 进行赋值 一个rvalue
  • reference qualifier: replace & or && after parameter list (像class 的const 定义类似), & 表示只能是lvalue call it , && 表示只能是rvalue call it:
    • noexcept类似, reference qualifier 必须同时出现于function declarationdefinition
    • 一个function 可以同时constreference qualifier. 但reference qualifier 必须跟在 const 后面
  • 就像 const function 可以被overload, overload a function based on its reference qualifer. 我们可以综合 reference qualifierconst 来区分一个成员函数的重载版本
    • const function version 通过一个有const, 一个没有const 来区分, 但是对于如果想用reference qualifier 区分 function(两个或两个以上有same name and same parameter list), 必须 provide a reference qualifier 在所有的functions or 所有都不提供

可以call lvalue or rvalue 的member function `

string s1 = "a value", s2 = "another"; 
auto n = (s1 + s2).find('a'); //ok

s1 + s2 = "wow!";//ok, 对一个rvalue 进行赋值

reference qualifier

class Test{
public:
    Test(int a_): a(a_){}
    int a;
    void getR() &&  {
        cout << " only rvalue can call "<<endl;
    }
    void getL() & {
        cout << " only lvalue can call "<<endl;
    }
};

auto rval = [] () -> Test { return Test(8); };
//也可以写成 auto rval = [] { return Test(8); };
    
Test lval(10);
rval().getR();//okay

lval.getR(); //error

lval.getL(); //okay

另一个例子

class Foo {
public:
    lvalues Foo &operator=(const Foo&) &; // may assign only to modifiable, 
    
    //只能是lvalue 在左侧的 assignment 

 };

Foo &Foo::operator=(const Foo &rhs) &
{
    // do whatever is needed to assign rhs to this object
    return *this;
}

Foo &retFoo(); //function returns a reference (lvalue)

Foo retVal(); //function returns by value (rvalue)

Foo i, j; // i, j are lvalues

i=j; // okay: i is lvalue 

retFoo() = j; // okay: retFoo() is lvalue

retVal() = j; // error: retVal() returns an rvalue

i = retVal(); // ok: we can pass an rvalue as the right-hand operand to assignment

reference qualifier 必须跟在const 的后面

class Foo {
public:
    Foo someMem() & const; // error: const qualifier must come first
    
    Foo anotherMem() const &; // ok: const qualifier comes first 

};

reference qualifier overload

class Foo { 
public:
    Foo sorted() &&; // // may run on modifiable rvalues 

    Foo sorted() const &; // 可用于任何类型的 Foo

private:
    vector<int> data;
};

// this object is an rvalue, so we can sort in place

Foo Foo::sorted() &&
{
    sort(data.begin(), data.end());
    return *this;
} 

// this object is either const or it is an lvalue; either way we can't sort in place 

//lvalue 是因为改了这个,可能改变其他对象的值,改不会报错

Foo Foo::sorted() const & {
    Foo ret(*this); // make a copy 
    
    sort(ret.data.begin(), ret.data.end()); // sort the copy 
    
    return ret; // return the copy

}

retVal().sorted(); // retVal() is an rvalue, calls Foo::sorted() && 

retFoo().sorted(); // retFoo() is an lvalue, calls Foo::sorted() const &

overload const reference 必须都加上 或者都不加上: 对于sorted function, 两个都没有加上 reference qualifier 是okay 的.

class Foo { 
public:
    Foo sorted() &&;
    Foo sorted() const; // error: must 加上 reference qualifier

    // Comp 用来比较int 值

    using Comp = bool(const int&, const int&);
    Foo sorted(Comp*); // ok: different parameter list

    Foo sorted(Comp*) const; // ok: neither version is reference qualified

};




14. overloaded operators

  • When an operator is a member function, the left-hand operand is bound to the implicit this parameter. The right-hand operand is passed as an explicit parameter(传递到function).
  • An operator function must either be a member of a class or have at least one parameter of class type:
    • 意味着我们不能改变built-in type operator 含义
  • 只能重载已有的operator, 不能加新的operator, 比如不可以overload ** 去执行幂操作
  • 对于重载运算符, precedence and associativity (优先率 和 结合律) 应保持一致, 比如 x == y + z; 永远等价于 x == (y + z)
  • 有四个 +,-,*,& 既可以是一元也可以是二元运算,根据number of parameters 推断定义是哪个
  • call operator function 可以直接用 operator data1 + data2; 也可以像普通function 一样 call overloaded operator function.operator+(data1, data2); data1.operator+=(data2);. 两种调用方法等价的
  • the comma, address-of, logical AND, and logical OR operators should not be overloaded.
    • 因为运算符指定了运算顺序, 而operator overloading 是一个function call, 运算顺序可能会变了
    • overloaded versions of && or || operators do not preserve short-circuit evaluation properties of the built-in operators. 用户发现他们熟悉的求值规则不见了
    • 因为这些运算符有了内置的含义,如果重载, behave differently from their normal meanings.
  • 如果一个class 定义了 == operator, 通常也应该定义 != as well, 同样如果一个class定义了 < operator, 也应该定义 all relational operators

error 去redefine built-in operators

// error: cannot redefine the built-in operator for ints 

int operator+(int, int);

Guideline of whether make the operator a class member or ordinary nonmember function

  1. assignment =, subscript [], call (), and member access arrow ->` must defined as members
  2. compound-assignement operators 比如+= 应该是 members
  3. Operators that change the state of their object or that are closely tied to their given type—such as increment, decrement, and dereference—usually should be members.
  4. Symmetric operators(provide mixed-type expression)—those that might convert either operand, such as the arithmetic, equality, relational, and bitwise operators—usually should be defined as ordinary nonmember functions. 希望还有混合类型的symmetric operator 比如 把 int 加上 double, 因为int 可以在 加号左侧 右侧都可以,

比如 加号(stringconst char* )被定义成了 string member function then. 相反如果定义成nonmember function, "hi" + s 等于 operator+("hi",s), 唯一的requirement是至少一个运算对象是class type, and both operands can converted to string

string s = "world";
string t = s+"!"; // ok 

string u = "hi" + s; // would be an error if + were a member of string

(b). Input and Output Operators

  • input and output function 必须是 nonmember function. 不能是member of own class. 因为如果是class member(left side is IO), 必须属于istream or ostream 类, 但是those class 是part of standard library, 不能加members to IO class
  • 如果 input and output access nonpublic data, declared it as friends

(1). Overloading the Output Operator «

  • first parameter of an output operator is a reference to a nonconst ostream object. nonconst 原因是 write to stream changes its state. 而reference 是因为IO对象 不能copy
  • 第二个paramemter 通常是 a reference to const class type that we want to print. const 是避免改变object
  • return its ostream reference parameter
  • The output operators for the built-in types do little formatting. 尤其不会打印换行符. 让user control the detials of their output

例子

ostream & operator << (ostream & os, const Sales_data & item){
    os << item.isbn() <<" "<<item.revenue;
    return os;
}

(2). Overloading the Input Operator »

  • first parameter is reference to stream from which it is to read.
  • 第二个 parameter 是 a reference to nonconst object which to read. 不是const 因为要read data into this object.
    • return its istream reference parameter
  • Input operators must deal with the possibility that the input might fail; output operators generally don’t bother.
  • 当读取操作发生错误时, 输入运算符应该负责从错误中恢复, to protect a user that ignores the posssibility of an input error, won’t generate misleading results
    • 如果错误发生错误, 应当set IO statefailbit, eofbit(file was exhausted), badbit(stream corrupted).

下面例子, 如果IO错误(比如输入不是数字, 或者hit end-of-file or some other error on input stream), 则运算符将非定对象重置为empty Sales_data

istream& operator>>(istream & is, Sales_data & item){
    double price; 
    is >> item.bookNo >> item.units_sold >> price;
    if (is) // check that the inputs succeeded 
        
        item.revenue = item.units_sold * price;
    else
        item = Sales_data(); // input failed: give the object the default 
    
    return is;
}

(c). Arithmetic and Relational Operators

  • we define the arithmetic and relational operators as nonmember functions, 允许左侧和右侧运算对象 转换.
  • Operators 不应该change state of either operand, so parameter is references to const
  • 如果定义了arithmetic operator, 通常也应该define compound assignment operator, 通常更efficient to define arithmetic operator to use compound assignment

arithmetic operator: use compound-assignment operator

Sales_data
operator+(const Sales_data &lhs, const Sales_data &rhs)
{
    Sales_data sum = lhs; // copy data members from lhs into 

    sum sum += rhs; 
    return sum;
}

1. Equality Operators

  • 如果class 需要判断两个object 是否相等,应该定义 == 而不是member function
  • operator should be transitive, 如果 a==b, b==ca==c
  • 如果定义了 == operator, 也应该定义 != operator
  • 相等和不等operator 应该把工作委托(delegate)给另外一个. 表示只有相等 or 不等 其中一个 实际比较 work, 另一个call that does the real work

下面例子展示了只有相等 或 不等一个do the real work

bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
  
  return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold &&
    lhs.revenue == rhs.revenue;
} 
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
    return !(lhs == rhs);   
}

2. Relational Operators

  • 定义了equality operator, 也应该定义relation operators, 因为一些container 用到了 operator <
  • Ordinarily the relational operators should:
    1. Define an ordering relation that is consistent with requirement for as a key to an associative container
    2. Define a relation that is consistent with == if the class has both operators. 如果 two objects are !=, one object should < the other
  • If a single logical definition for < exists(如果存在唯一一种逻辑可靠的 <定义), classes usually should define the < operator. However, if the class also has ==, define < only if the definitions of < and == yield consistent results.

(d). Assignment Operators

  • 除了copy assignment 和 move assignment operators 把同一类的赋值, 也可以定义additional assignment operators that allow 别的类型作为 right-hand operand
  • library (e.g.vector) classs 还定义了 a braced list of elements vector<string> v = {"a", "an", "the"};
  • Assignment operators can be overloaded. Assignment operators, regardless of parameter type, must be defined as member functions. ordinarily compound-assignment operators 也应该定义成 member function
class StrVec { 
public:
    StrVec &operator=(std::initializer_list<std::string>);
    //其他成员和上面定义一样
};

StrVec & StrVec::operator=(std::initializer_list<string>il){
    auto data = alloc_n_copy(il.begin(), il.end());
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

compound-assignment operator

Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; 
    revenue += rhs.revenue; 
    return *this;
}

(e). Subscript Operator

  • define the subscript operator operator[]
  • The subscript operator must be a member function.
  • To be compatible with the ordinary meaning of subscript, the subscript operator usually returns a reference to the element that is fetched.
    • 因为返回时reference, 所以 subscript 可以是assignment 的either side.
    • good idea to define both const and nonconst version of subscript operator. (因为可以下表运算结果reference可以是 赋值运算任意一边)

不能对返回const reference 的subscript operator 进行赋值运算

class StrVec { 
public:
    std::string& operator[](std::size_t n) { return elements[n]; }
    const std::string& operator[](std::size_t n) const { return elements[n]; }

private:
    std::string *elements; // pointer to the first element in the array 

};

const StrVec cvec = svec; // copy elements from svec into cvec

if (svec.size() && svec[0].empty()) {
    svec[0] = "zero"; // ok: subscript returns a reference to a string

    cvec[0] = "Zip"; // error: subscripting cvec returns a reference to const 

}

(f). Increment and Decrement Operators

  • 因为 increment (++) 和 decrement (–) change the state of the object on which they operate, our preference is to make them member functions
  • prefix return a reference, postfix return a value
    • To be consistent with the built-in operators, the postfix operators should return the old (unincremented or undecremented) value. That value is returned as a value, not a reference.
  • 区分prefix and postfix:
    • postfix version 有一个extra(unused, so not give it a name) parameter of type int. When we use a postfix operator, the compiler supplies 0 as the argument for this parameter, 尽管可以使用这个extra 0, 但实际不这么做
    • The postfix versions have to remember the current state of the object before incrementing the object
    • Prefix 需要check if increment/decrement is safe and either throw exception or increment/decrement, Postfix 不需要explicitly check,因为postfix 内call prefix which do the check.

Defining Prefix Increment/Decrement Operators

increment check 如果currr已经到达了vector的末尾, check 将抛出异常。Decrement 如果curr 已经是0了, 那么传递给check的值是一个非常大的positive 无效的value

class StrBlobPtr { 
public:
// increment and decrement 
    
    StrBlobPtr& operator++(); 
    StrBlobPtr& operator--(); // other members as before

};

// prefix: return a reference to the incremented/decremented object 

StrBlobPtr& StrBlobPtr::operator++()
{
    // if curr already points past the end of the container, can't increment it 
    
    check(curr, "increment past end of StrBlobPtr"); 
    ++curr; 
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
    // / if curr is zero, decrementing it will yield an invalid subscript
    
    --curr; 
    check(-1, "decrement past begin of StrBlobPtr"); //check if -1>=curr(unsigned), 如果大于throw error 
    
    return *this;
}

postfix version don’t check increment because prefix version 内check the increment

StrBlobPtr StrBlobPtr::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check 

    StrBlobPtr ret = *this; // save the current value

    ++*this; // advance one element; prefix ++ checks the increment

    return ret; // return the saved state

} 
StrBlobPtr StrBlobPtr::operator--(int)
{
    // no check needed here; the call to prefix decrement will do the check 

    StrBlobPtr ret = *this; // save the current value

    --*this; // move backward one element; prefix -- checks the decrement

    return ret; // return the saved state 

}

Calling the Postfix Operators Explicitly

StrBlobPtr p(a1); // p points to the vector inside a1 

p.operator++(0); // call postfix operator++ 

p.operator++(); // call prefix operator++

(g). Member Access Operators

  • The dereference (*) and arrow (->) operators are often used in classes that represent iterators and in smart pointer classes
  • Operator arrow must be a member(function). The dereference operator is not required to be a member but usually should be a member as well.
  • When we write point->mem, point must be a pointer to a class object or class with an overloaded operator->. 根据point类型不同, writing point->mem resolve 步骤是:
    1. 如果 pointpointer type ,则用(*point).mem;.
    2. 如果pointclass type with overloaded operator-> 则用 point.operator->()mem; 来fetch mem
      • 如果operator->function return pointer type, goto step 1
      • 如果返回的 res 是class, 继续step2, point.operator->()mem 转化为 res.operator->()mem; 直到fetch的是pointer type or some error

比如下面例子, 最好获得是size的number, 不建议这么做, 因为breaks encapsulation

struct size {
    int width, height;
    size() : width(640), height(480) { }
};
struct metrics {
    size s;
    size const* operator ->() const {
        return &s;
    }
};
struct screen {
    metrics m;
    metrics operator ->() const {
        return m;
    }
};

int main() {
    screen s;
    std::cout << s->width << "\n";
}

值得注意

  • deference 返回时 reference, 而 arrow 一般返回是 pointer
  • 两个function 是const member, 因为fetch an element 不改变state of a StrBlobPtr. 这些operator 返回reference or pointer to nonconst string, 因为StrBlobPtr(它的constructor是 StrBlob &) 绑定的是nonconst 的 StrBlob
class StrBlobPtr { 
public:
    std::string& operator*() const
    { 
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points

    } 
    
    std::string* operator->() const
    { 
        // delegate the real work to the dereference operator 

        return & this->operator*();
    }
};

StrBlob a1 = {"hi", "bye", "now"};
StrBlobPtr p(a1); // p 指向a1中的vector

*p = "okay"; // assigns to the first element in a1

cout << p->size() << endl; // prints 4, the size of the first element in a1

cout << (*p).size() << endl; // equivalent to p->size()

(h). Function-Call Operator

  • overload the call operator allow objects to be used as function
    • 可以store state, more flexible than ordinary functions.
    • Objects of classes that define the call operator 被叫做 function objects.
  • The function-call operator must be a member function, can define 多个 call operator, each of which must differ as to the number or types of their parameters.
  • Function objects(定义了function-call operator) are most often used as arguments to the generic algorithms,
  • Lambdas Are Function Objects
    • 除了function objects, 也可将lambda function 用于 generic algorithms.
    • When we write a lambda, the compiler translates that expression into an unnamed object of an unnamed class . The classes generated from a lambda contain an overloaded function-call operator (parameter list and function body are the same as lambda)
      • by default, lambdas 不能改变 captured variables.As a result, the function-call operator in class generated from lambda is const member function. If lambda declared as mutable, then call operator is not const
    • 当lambda captures a varaibles by reference, 是由程序确保lambda 执行时引用所引对象确实存在. Therefore, the compiler is permitted to use the reference directly without storing that reference as a data member in the generated class.
    • 当lambda captures by value are copied into the lambda. These classes have a constructor to initialize these data members from captured values, so class store these values in class.
    • Classes generated from a lambda expression have a deleted default constructor, deleted assignment operators, and a default destructor. Whether the class has a defaulted or deleted copy/move constructor depends in the usual ways on the types of the captured data members

下面例子 function call operator returns its absolute value

struct absInt {
    int operator()(int val) const {
        return val < 0 ? -val : val;
    }
};

int i = -42;
absInt absObj; 
int ui = absObj(i); // passes i to absObj.operator()

Function-object classes often contain data members that are used to customize the operations in the call operator. 下面的for_each 会initialize from cerr and a new line character.

class PrintString{
public:
    PrintString(ostream &o = cout, char c = ' '): os(o), sep(c) { }
    void operator()(const string &s) const { os << s << sep;
private: 
    ostream &os;
    char sep;
};

PrintString printer; // uses the defaults; prints to cout 

printer(s); // prints s followed by a space on cout

PrintString errors(cerr, '\n');

errors(s); // prints s followed by a newline on cerr

for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));

1. Lambdas Are Function Objects

stable_sort(words.begin(), words.end(),
        [](const string &a, const string &b)
        {return a.size() < b.size(); });

//acts like an unnamed object of a class that would look something like

class ShorterString {
public:
bool operator()(const string &s1, const string &s2)
    const{ return s1.size() < s2.size(); } 
};

//We can rewrite the call to stable_sort to use this class instead of the lambda expression:

stable_sort(words.begin(), words.end(), ShorterString());

Lambda capture by value, class generated 需要有constructor. This synthesized class does not have a default constructor, 需要提供argument

auto wc = find_if(words.begin(), words.end(),
    [sz](const string &a) {return s.zie() >= sz;});

//would generate a class that looks something like

class SizeComp {
    SizeComp(size_t n): sz(n) { } // parameter for each captured variable
    
    // call operator with the same return type, parameters, and body as the lambda 

    bool operator()(const string &s) const { return s.size() >= sz; }
private:
    size_t sz; // a data member for each variable captured by value 

};    

// to use this class, must pass an argument: 

auto wc = find_if(words.begin(), words.end(), SizeComp(sz));

2. Library-Defined Function Objects

  • standard library defines a set of classes that represent the arithmetic, relational, and logical operators. 每一个class 定义了call operator. 比如 add, modulus(%), equal_to
  • library function objects guarantees work for pointers. 比如sort pointers based on their address in memory. Although it would be undefined for us to do so directly, we can do so through one of the library function objects
  • associative containers use less<key_type> to order their elements. 比如可以define set of pointers or use a pointer as key in map without specify less directly.
plus<int> intAdd; // function object that can add two int values 

negate<int> intNegate; // function object that can negate an int value 

// uses intAdd::operator(int, int) to add 10 and 20

int sum = intAdd(10, 20); //30

sum = intAdd(10, intNegate(10)); // sum = 0

使用function-object classes override the default operator used by an algorithm. 比如: sort 用的default operator < which sorts the sequence into ascending order. To sort into descending order, pass greater

sort(svec.begin(), svec.end(), greater<string>());

function-objects 可以用pointer比较 addresss in memory

vector<string *> nameTable; 
// 老版error, 新版不是error : the pointers in nameTable are unrelated, so < is undefined 

sort(nameTable.begin(), nameTable.end(),
    [](string *a, string *b) { return a < b; });

// ok: library guarantees that less on pointer types is well defined 

sort(nameTable.begin(), nameTable.end(), less<string*>());

3. Callable Objects and function

  • callable objects: functions and pointers to functions, lambdas, objects created by bind, and classes that overload the function-call operator(他们彼此间type 不一样).
  • two callable objects(Different types) with different types may share the same call signature. 比如用map<string, int(*)(int,int)> 不能存储 callable objects with different type but the same callable signature
  • 解决上面的问题可以用 funtion defined in functional header
    • call signature inside angle brackets: 比如function<int(int, int)>
    • We cannot (directly) store the name of an overloaded function in an object of type function. 比如说一样的名字,但是属于不同类, 具体看下面例子
      • One way to resolve the ambiguity is to store a function pointer(因为function pointer 带着类型) instead of the name of the function:
      • 另外一种方法, we can use a unamed lambda to disambiguate
Operations on function
Syntax Description
function<T> f f is null function object that can store callable objects with a call signature that equaivalent to function type T (i.e. T is retType (args) )
function<T> f(nullptr) Explicityly construct a null function
function<T> f(obj); stores a copy of the callable obj in f
f Use f as a condition; true if f holds a callable object; false otherwise
f(args) Calls he object in f passing args  
  下面是 Types defined as member of function<T>
result_type The type returned by this function types’s callable object
argument_type
first_argument_type
second_argument_type
Types defined when T has exactly one or two arguments. 如果T只有一个argument, 则argument_type 是该类型同义词; 如果T有两个argument, 则first_argument_typesecond_argument_type 分别代表两个argument type

two callable objects with different types may share the same call signature as if they had the same type. 比如call signature int(int, int), 下面的例子add, moddiv 都是接受两个ints, return 一个 int

// ordinary function

int add(int i, int j) { return i + j; }

// lambda, which generates an unnamed function-object class

auto mod = [](int i, int j) { return i % j; }; 

// function-object class

struct div {
    int operator()(int denominator, int divisor) {
        return denominator / divisor;
    }
};

我们希望define a function table to store “points” to these callable, 当需要特定操作, look in table to find function to call, 比如定义 map 如下, 可以加入add, 但是不能加入mod or div, mod is a lambda, and each lambda has its own class type

// maps an operator to a pointer to a function taking two ints and returning an int 

map<string, int(*)(int,int)> binops;
// ok: add is a pointer to function of the appropriate type 

binops.insert({"+", add}); //

binops.insert({"%", mod}); // error: mod is not a pointer to function

function example

function<int(int, int)> f1 = add; // function pointer 

function<int(int, int)> f2 = div(); // object of a function-object class

function<int(int, int)> f3 = [](int i, int j) { return i * j; }; // lambda

cout << f1(4,2) << endl; // prints 6

cout << f2(4,2) << endl; // prints 2 

cout << f3(4,2) << endl; // prints 8

可以用function type redefine our map

// table of callable objects corresponding to each binary operator 

// all the callables must take two ints and return an int

// an element can be a function pointer, function object, or lambda 

map<string, function<int(int, int)>> binops = {
    {"+", add}, // function pointer

    {"-", std::minus<int>()}, // library function object
 
    {"*", [](int i, int j) { return i * j; }}, // unnamed lambda 
    
    {"%", mod}, // named lambda object

     {"/", div()} }; // user-defined function object

binops["+"](10, 5); // calls add(10, 5)

binops["-"](10, 5); // uses the call operator of the minus<int> object

binops["/"](10, 5); // uses the call operator of the div  object

binops["*"](10, 5); // calls the lambda function object 

binops["%"](10, 5); // calls the lambda function object

We cannot (directly) store the name of an overloaded function in an object of type function, 比如下面有两个add, 放进去的add 不知道哪个add

int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&); 
map<string, function<int(int, int)>> binops;
binops.insert( {"+", add} ); // error: which add?

//用function pointer store 消除问题

int (*fp)(int,int) = add; // pointer to the version of add that takes two ints

binops.insert( {"+", fp} ); // ok: fp points to the right version of add


//另一种方法用lambda:

// ok: use a lambda to disambiguate which version of add we want to use 

binops.insert( {"+", [](int a, int b) {return add(a, b);} } );

(i). Overloading, Conversions, and Operators

1. Conversion Operators

  • We can use non-explicit constructor that can be called with one argument defines an implicit conversion. Convert object from argument’s type to class type. 也可以定义conversion from the class; define a conversion from a class type by defining a conversion operator.
  • operator type() const;: conversion operator is a member function that converts a value of a class type to a value of some other type(不能是void, 也不能是array or function type). Conversions to pointer types(data or function pointers) 或者 reference types 是可以的
  • Conversion operators have no return type and no parameters
  • Must be defined as member functions
  • Conversion operations ordinarily should not change the object they are converting. As a result, conversion operators usually should be defined as const members.
  • Compiler 只能apply 一步conversion at a time, 但是implicit user-defined conversion can be preceded or followed by a standard conversion(built-in conversion double to int,而 const char* 到 string 不算built-in)
  • Caution: Avoid Overuse of Conversion Functions: 如果不存一对一mapping 关系的时候不要定义
    • 一般很少提供类型转换,但是一个例外是 for classes to define conversions to bool. 但早期的版本 因为bool 算arithmetic type, 会导致convert to bool 再convert to arithmetic, 导致意想不到的结果,
    • explicit Conversion Operators: 为防止上面说的情况发生, 不会发生自动转换. The compiler won’t (generally) use an explicit conversion operator for implicit conversions. must do so explicitly through a cast.
      • 这个规定存在一个例外,如果表达式被用作条件, an explicit conversion will be used implicitly to convert an expression used as
        • The condition of an if, while or do statement, condition expression in a for statement,
        • An operand to the logical NOT (!), OR (||), or AND (&&) operators or 条件逻辑表达式 (?:) operator

比如下面例子既定义了SmallInt 到int 转换, 也定义了从 int 到 SmallInt 转换. defines conversions to and from its type

class SmallInt { 
public:
    SmallInt(int i = 0): val(i)
    {
        if (i < 0 || i > 255) 
            throw std::out_of_range("Bad SmallInt value");
    }
    operator int() const { return val; }
private: 
    std::size_t val;
};       

SmallInt si;
si = 4; // implicitly converts 4 to SmallInt then calls SmallInt::operator=

si + 3; // implicitly converts si to int followed by integer addition

Although compiler apply only one user-defined conversion at a time. an implicit user-defined conversion can be preceded or followed by a standard (built-in) conversion. 比如下面例子 可以pass any 算数类型 to the SmalInt constructor. 也可以将SmallInt 转换成int 然后 convert int 到其他arithmetic type

//built in conversion double -> int

SmallInt si = 3.14; // calls SmallInt(int) constructor

// the SmallInt conversion operator converts si to int;

si + 3.14; // int 再用 built-in conversion to double 

Conversion operation no return type, no parameter list, always const function

class SmallInt; 
operator int(SmallInt&); //error: nonmember


class SmallInt { 
public:
    int operator int() const; //error: 有return type 
    
    operator int(int = 0) const; //error: parameter list 不为空
    
    operator int*() const { return 42; } // error: 42 is not a pointer
    
};

一些早期的版本bool 算arithmetic type (所以转换成bool -> arithmetic type ), 所以class convert to bool (not explicit) 有意想不到的后果, 比如ifstream had convertion to bool. 尝试用 output operator on an input stream, 因为没有 << 定义 给 istream, 应该有error, 但是use bool conversion operator to convert cin to bool. 导致 bool to int. 所以 42 会shift left 1 or 0

int i = 42;
cin << i; // this code would be legal if the conversion to bool were not explicit!

explicit Conversion Operators: The compiler won’t (generally) use an explicit conversion operator for implicit conversions. 必须用 cast 才能转换

SmallInt si = 3; // ok: the SmallInt constructor is not explicit

si + 3; // error: implicit is conversion required, but operator int is explicit 

static_cast<int>(si) + 3; // ok: explicitly request the conversion

早期版本IO通过定义了向 void*类型转换,避免以上问题( convert to void * which is nullptr if the stream operation failed, and non-null if the stream is still good.), C++11 通过explicit conversion to bool. The condition in while 执行了input operator reads value and return cin. cin implicitly converted by the istream operator bool conversion function, return true if conditon state of cin is good, and false otherwise

while (std::cin >> value)

Explicit call conversion operator

class Test{
public:
    operator string() const {
        return "123";
    }
};
Test a;
cout << a.operator string()<<endl; //ok

2. Avoiding Ambiguous Conversions

  • If a class has one or more conversions, Important to ensure only one way convert from class type to target type. 两种方式multiple conversion paths (都很可能产生ambiguity)
    • 第一种provide mutual conversion (converting constructor and conversion operator)
      • 比如 Foo has a constructor from Bar, 不要给 Bar conversion operator to Foo
    • 第二种是: define multiple conversions from or to types that are themselves related by conversions(比如 two arithmetic types).
      • Do not define overloaded versions of conversion constructor that take arithmetic types. 比如 定义了 int to Foo, 不要定义double to Foo
      • Do not define conversion operators to more than one arithmetic type. Let the standard conversions provide conversions to the other arithmetic types. 比如定义了 Foo to int, 不要定义 Foo to double
    • 如果定义了多种方式conversions, 之所以ambiguity, 因为conversions have the same rank(the rank of standard conversion)
    • 如果有ambiguity, 不能用cast 解决问题
  • Caution: Conversions and Operators
    • Ordinarily, it is a bad idea to define classes with mutual conversions or to define conversions to or from two arithmetic types.
    • The easiest rule of all: 除了可以定义 an explicit conversion to bool, avoid defining conversion functions and limit nonexplicit constructors.
  • Need to use a constructor or a cast to convert an argument in a call to an overloaded function frequently is bad design. 注意下面例子
    • Call to an overloaded function, if two (or more) 转化为不同type的 都可行的 (比如 10, short to Foo 还是 double to Bar), 是ambiguity, rank of any standard conversion 不会被考虑. 只有当同一个 type user-defined conversion时候(比如是 short to Foo 还是 double to Foo时候), rank of standard conversion 才 被考虑

下面例子obtain A 可以从 A的constructor or B’s conversion operator. the call to f is ambiguous. error. 不能用 cast 来解决ambiguity

struct B;//根据compile 顺序, 需要首先declare

struct A {
    A() = default; 
    A(const B&);// converts a B to an A 

};
struct B{
    operator A() const; // also converts a B to an A

};

A f(const A&); //function parameter 是A, 返回 A

B b;
A a = f(b); // error ambiguous: f(B::operator A())

            // or f(A::A(const B&))


// If want to make the call, explicitly call the conversion operator or constructor:

A a1 = f(b.operator A()); // ok: use B's conversion operator 

A a2 = f(A(b)); // ok: use A's constructor

Ambiguities

  • 当call f2时候,两个类型转换都可以使用, 不管是long double -> int or long double -> double 而且哪个转换没有比另一个更好, the call is ambiguous.
  • initialize a2 from long。 Neither constructor is an exact match for long. long -> double followed by A(double) or long -> int, followed by A(int). the call is ambiguous.
  • 如果conversion 的 rank 不一样, 则有better match. 比如short to int 是preferred to short to double , 所以不会有ambiguity
struct A {
    A(int = 0);     // usually a bad idea to have two

    A(double); // conversions from arithmetic types


    operator int() const; // usually a bad idea to have two operator 

    double() const; // conversions to arithmetic types 

};
void f2(long double);//function 
A a;
f2(a); // error ambiguous: f(A::operator int())

        // or f(A::operator double()) 

long lg;
A a2(lg); // error ambiguous: A::A(int) or A::A(double)

short s = 42;
// promoting short to int is better than converting short to double 

A a3(s); // uses A::A(int)

Overloaded Functions and Converting Constructors

when we call an overloaded function. 如果两个或多个类型转换都提供了同一种可行的匹配,这些类型转换一样好. 下面例子ambiguity 因为overload functions take parameters 的class type 定义了一样的 converting constructor

struct C { 
    C(int);
};
struct D {
    D(int);
};
void manip(const C&);
void manip(const D&);
manip(10); // error ambiguous: manip(C(10)) or manip(D(10))

//可以消除ambiguity by explicitly constructing the correct type

manip(C(10)); //ok: calls manip(const C&)

下面例子证明: 当 call to an overloaded function, if two (or more) user-defined conversions provide a viable match, 不会用 rank of any standard conversion 去看which is better match, 比如下面的 int 对于 10 是better match, double 对于10 需要额外的standard conversion, compiler 仍然认为是ambiguous, the compiler will still flag this call as an error.

struct E { E(double);
// other members
};
void manip2(const C&);
void manip2(const E&);
// error ambiguous: two different user-defined conversions could be used

manip2(10); // manip2(C(10) or manip2(E(double(10)))

3. Function Matching and Overloaded Operators

  • Providing both conversion functions to an arithmetic type and overloaded operators for the same class type may lead to ambiguities between the overloaded operators and the built-in operators.
  • When we call a named function, member and nonmember functions with the same name 不会 overload, 因为syntax 不同(call member type 需要through object, reference or pointer). 但是call overloaded operator , nothing to indicate whether using a member or nonmember function.

the expression a sym b might be, 第一种是member function, 第二种是nonmember function. 不像普通function calls, 我们不能区分whether we’re calling a nonmember or member function .

a.operatorsym (b); // a has operatorsym as a member function 

operatorsym(a, b); // operatorsym is an ordinary function

下面例子, 第一个use overloaded versionf of + that takes two SmallInt values. 第二个addition is ambiguous, 因为我们convert 0 to a SmallInt 也可以把 s3 convert to int and use built-in additon on ints

class SmallInt { 
    friend
    SmallInt operator+(const SmallInt&, const SmallInt&); 
public:
    SmallInt(int = 0); // conversion from int

    operator int() const { return val; } // conversion to int private:
private:
    std::size_t val;
};
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // uses overloaded operator+ 

int i = s3 + 0; // error: ambiguous





(15). Object-Oriented Programming

(a). OOP: An Overview

  • Object-oriented programming key ideas is: data abstraction, inheritance and dynamic binding.
    • data abstraction: define classes that separate interface from implementation.
    • Inheritance: define classes that model relationship among similar types
    • dynamic binding: use objects of these types(similar types) while ignoring the details of how they differ

Dynamic Binding:

the decision as to which version to run depends on the type of the argument, 在run time 选择函数版本. Therefore, dynamic binding is sometimes known as run-time binding.

  • dynamic binding happens when a virtual member function is called through a reference or a pointer to a base-class type. 比如下面例子 Use the same code to process objects of either type Quote or Bulk_quote interchangeably. item is reference const Quote & , 可以是 Quote object or Bulk_quote object. 因为net_price 是virtual function, ther version of net_price that is run will depend on the type of the object that we pass to print_total
class Quote { 
public:    
     std::string isbn() const;
    virtual double net_price(std::size_t n) const;
};
class Bulk_quote : public Quote { // Bulk_quote inherits from Quote

    double net_price(std::size_t) const override;
};

double print_total(ostream &os, const Quote &item, size_t n)
{
// depending on the type of the object bound to the item parameter 

// calls either Quote::net_price or Bulk_quote::net_price 

double ret = item.net_price(n);
    os << "ISBN: " << item.isbn() // calls Quote::isbn

    << " # sold: " << n << " total due: " << ret << endl; 
    return ret;
}
print_total(cout, basic, 20); // calls Quote version of net_price 

print_total(cout, bulk, 20); // calls Bulk_quote version of net_price

(b). Defining Base and Derived Classes

base class:

  • a base class must distinguish the functions it expects its derived classes to override (virtual) VS expects its derived classes to inherit without change(直接继承不要改变的).
  • A base class must be defined, not just declared, before we can use it as a base class: 因为derived class 可能contains and use memeber it inherits from base class. 使用这些 members, derived lass must know what they are. 所以重要的implication是: impossible to derive a class from itself

derived class:

  • Derived Classes 声明时候 不用include its derivation list. 比如 class Foo; ok的, 但 class Foo: public bar; 是error的
  • A derived class must specify the class(es) from which it inherit. It does so in derivation list (继承了哪个class, 是public, private, protected): a colon followed by a comma-separated list of base classes each of which may have an optional access specifier
  • A derived class constructor initializes its direct base class only. 比如 C继承B, B继承A, 那么 C 只用initialize B 的constructor, 不用initialize A 的, A 的constructor 由 B 来initialize
  • 尽管derived class contains member from inheritance from base, 但cannot directly initialize those members. a derived class must use a base-class constructor to initialize its base-class part. Base-class part initialized along witht data members of derived class, during the initialization phase of the constructor
    • The base class is initialized first, and then the members of the derived class are initialized in the order in which they are declared in the class.
    • 遵循base-class interface: 即使可以通过inheritance 给public/protected data 赋值, 最好还是通过base-class constructor initialize member(should respect the interface of its base class)

inheritance:

  • The fact that a derived object contains subobjects for its base classes is key to how inheritance works.
  • Standard 没有说明how derived objects 怎么存储, The base and derived parts of an object are not guaranteed to be stored contiguously
  • a base class at the root of the hierarchy. Inheriting classes are known as derived classes.
    • base class: 定义 common members. derived clas define memeber that are specific to derived class
  • A direct base class is named in the derivation list. An span style=”color:red”>indirect base</span> is one that a derived class inherits through its direct base class. The most derived object contains a subobject for its direct base and for each of its indirect bases.

a derived-class constructor uses its constructor initializer list to pass arguments to a base-class constructor

Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) :
        Quote(book, p), min_qty(qty), discount(disc) { } 
};

用作base class 必须被defined not just declared.

class Quote; // declared but not defined

// error: Quote must be defined

class Bulk_quote : public Quote { ... };

一个class 可以同时是base class, 也可以是 derived class. 下面例子 Base is a direct base to D1 and an indirect base to D2.

classBase{/* ... */};
class D1: public Base { /* ... */ }; 
class D2: public D1 { /* ... */ };

可以阻止Inheritance by define final

class NoDerived final { /* */ }; // NoDerived can't be a base class class 

Base { /* */ };

// Last is final; we cannot inherit from Last

class Last final : Base { /* */ }; // Last can't be a base class 

class Bad : NoDerived { /* */ }; // error: NoDerived is final class 

Bad2 : Last { /* */ }; // error: Last is final

static Members

  • base class 定义了 static member, only one such member defined for the entire hierarchy. 不管几个继承了base class, 只有一个instance of each sstatic member.
  • 如果staticprivate, derived class 不能用它
  • 如果static member is accessible, we can use a static member through either base or derived
class Base { 
public:
    static void statmem();
};

class Derived : public Base {
    void f(const Derived&){
        Base::statmem(); // ok: Base defines statmem 
        
        Derived::statmem(); // ok: Derived inherits statmem
    
        // ok: derived objects can be used to access static from base 
        
        derived_obj.statmem(); // accessed through a Derived object 
        
        statmem(); // accessed through this object

    }
};

(c). Derived-to-Base Conversion

  • Ordinarily, we can bind a reference or a pointer only to an object that has the same type as the corresponding reference or pointer or to a type that involves an acceptable const conversion
    • class type 有个例外: bind a pointer or reference to a base-class type to an object of a type derived from that base class. 只能derived-to-base, 不能convert base to derived. - the compiler will apply the derived-to-base conversion implicitly.
      • derived to base conversion 之所以存在因为每一个derived class 都包含base-class part (用base class type 的reference or pointer 可以绑定到上面)
      • 但是反过来对于base-class, 没有这个保证, A base-class object can exist either as an independent object or as part of a derived object. 如果base object is not part of derived object 只有member defined in base, 没有member defined by derived class
      • cannot convert from base(a base pointer or reference is bound to a derived object) to derived. 当使用绑定derived 的base reference/pointer 去convert to derived 是不可以的
    • important implication: When we use a reference (or pointer) to a base-class type, we don’t know the actual type of the object to which the pointer or reference is bound. 可以是base 也可以是derived 的type
    • 因为assignment operator, copy constructor 接受const reference of class type, the derived-to-base conversion lets us pass a derived object to a base-class copy/move operation. Only the base-class part of the derived object is copied, moved, or assigned. The derived part of the object is ignored (忽略 derived class 部分被 sliced down(切掉了).)
    • Like built-in pointers, the smart pointer classes support the derived-to-base conversion—we can store a pointer to a derived object in a smart pointer to the base type.
  • 没有conversion from a base-to-derived class type. 但是possible convert an object of a derived class to base-class type. However, such conversions may not behave as we might want.
    • 如果当我们知道conversion is safe from base to derived, 可以用 static_cast override the compiler

static type VS dynamic type

  • static type of an expression: known at compile time : it is the type with which a variable is declared or that an expression yields.
  • The dynamic type is the type of the object in memory that at the variable or expression represents. The dynamic type may not be known until run time.
  • static type of a pointer or reference to a base class may differ from its dynamic type.

Derived-to-Base Conversion:

Quote item; 
Bulk_quote bulk; 
Quote *p = &item; // p bound to the Quote object

p = &bulk; // p bound to the Quote part of bulk

Quote &r = bulk; // r bound to the Quote part of bulk

下面例子, static type of item differ from its dynamic type. As we’ve seen, the static type of item is Quote&, but in this case the dynamic type is Bulk_quote

double ret = item.net_price(n);

对于base class might or might not be part of a derived object, there is no automatic conversion from the base class to its derived class(s): 因为Derived member可能不在base中. Compiler no way to know that conversion is safe at run time. Compiler 仅仅look at static types of pointer or reference to 判断if conversion legal. 如果base class 有one or more virtual functions, can use dynamic_cast (cover at 19.2.1) to request a conversion that checked at run time.

Base base;
Derived* bulkP = &base; // error: can't convert base to derived 

Derived& bulkRef = base; // error: can't convert base to derived

cannot convert from base(a base pointer or reference is bound to a derived object) to derived. 即使base pointer 的dynamic type 是 derived type.

Derived bulk;
Base *itemP = &bulk; // ok: dynamic type is Bulk_quote 

Derived *bulkP = itemP; // error: can't convert base to derived

Copy / Move operation 的parameters const reference to class type. 因为是reference, the derived-to-base conversion lets us pass a derived object to a base-class copy/move operation; 这些operation not virtual. When we pass a derived object to a base-class constructor/assignment operator, constructor/operator knows only about the member of the base class. 比如我们定义的class, 有synthesized versions of copy and assignment. That constructor only knows QuotebookNoprice. copies those member from Base and ignore the members that are part of the Derived

Derived bulk; // object of derived type

Base item(bulk); // uses the Quote::Quote(const Quote&) 

item = bulk; // calls Quote::operator=(const Quote&)

重要的点:

  • The conversion from derived to base applies only to pointer or reference types
  • No implicit conversion from the base-class type to the derived type.
  • Like any member, the derived-to-base conversion may be inaccessible due to access controls.
  • 可以copy, move, assign an object of derived type to base-type object. 但是只copy, move, assign member in the base-class part of object.

(d). Virtual / Polymorphism

Virtual

  • When we call a virtual function through a pointer or reference, the call will be dynamically bound. Depending on reference or pointer 绑定 object type, the version in the base class or in one of its derived classes will be executed.
  • Any nonstatic member function, other than a constructor, 可以是 virtual. virtual 只能用于the declaration inside the class, outside class定义时不能用
    • virtual function in base class is implicitly virtual in all derived classes. 在 derived class virtual function, keyword virtual 加不加都可以
    • Member functions not declared virtual are resolved at compile time
  • derived class override inherited virtual function 必须有一样的parameter type as the base-class function
    • return type 一般也要match, 一个例外是 return a reference / pointer to types that are themselves related by inheritance. 比如 D 继承 B, B 的virtual 返回 B*, D virtual 可以返回 D*, 并且在return typederived-to-base conversion from D to B is accessible
    • override: explicitly note that function override a virtual that it inherits. compiler will reject a program if a function marked override does not override an existing virtual function
      • override 只能用于 virtual function
    • final: - Preventing Inheritance. 通过定义 class as final: class NoDerived final, class 不能再被 inherited
      • 把function 定义成 final 表示不能再override, 比如 C 继承 B, B继承A, B override 的virtual from A as final 表示 C中不能再override 这个function
    • finaloverridespecify parameter list, const, reference qualifier 之后
  • 可以redefine a function in derived class the same name as virtual in base class 但是 不同的 parameter list. Compiler 会认为这个function 不是override base class virtual function
  • Virtual functions that have default arguments should use the same values in the base and derived classes.
    • default argument bind at compiled time, derived 的default argument 不会override base 的default
  • 有些情况prevent dynamic binding of a call to virtual function, 可以force call to use 特定的virtual(不用derived 用base的), 可以用scope operator. This call will be resolved at compile time.
    • 一般情况只有member functions or friends 才用scope operator 回避dynamic binding
    • The most common case is when a derived-class virtual function calls the version from the base class. base-class version might do work common to all types in the hierarch. Derived classes would do whatever additional work
    • If a derived virtual function that intended to call its base-class version omits the scope operator, the call will be resolved at run time as a call to the derived version itself, resulting in an infinite recursion
  • virtual functions must always be define. 因为dynamic binding不知道call 哪个function at runtime. 通常上, 如果我们不需要哪个function, 无须定义.

Polymorphism

  • The key idea behind OOP is polymorphism(from Green meaning “many forms”). inheritance as polymorphic types. we can use the “many forms” of these types while ignoring the differences among them. The fact that the static and dynamic types of references and pointers can differ is the cornerstone(根本所在) of how C++ supports polymorphism.
    • 比如使用 reference or pointer to the base class, 不知道type(base type or derived type) until runtime. 如通过 pointer or reference of base class call virtual function. The version of virtual function 由 the reference or pointer 绑定对象的真实类型决定
    • On the other hand,calls to nonvirtual functions through pointer/reference or calls to any function (virtual or not) on an object(plain—nonreference and nonpointer—type) are bound at compile time.

下面例子中case3 比较重要, 因为only copy derived 中的base-class 的part. case3中base is plain (non pointer and non reference) that call is bound at compile time. We can change the value (i.e., the contents) of the object that base represents, but there is no way to change the type of that object. 因此call is resolved at compile time

Quote base("0-201-82470-1", 50);
print_total(cout, base, 10); // calls Quote::net_price 

Bulk_quote derived("0-201-82470-1", 50, 5, .19);
print_total(cout, derived, 10); // calls Bulk_quote::net_price

//case 3

base = derived; // copies the Quote part of derived into base 

base.net_price(20); // calls Quote::net_price

override: 只能用于virtual function

struct B {
    virtual void f1(int) const; 
    virtual void f2();
    void f3();
};
struct D1 : B 
{
    void f1(int) const override; // ok: f1 matches f1 in the base 
    
    void f2(int) override; // error: B has no f2(int) function

    void f3() override; // error: f3 not virtual

    void f4() override; // error: B doesn't have a function named f4
}

struct D2 : B {
    // inherits f2() and f3() from B and overrides f1(int)
    
    void f1(int) const final; // subsequent classes can't override f1 (int)

};
struct D3 : D2 {
    void f2(); // ok: overrides f2 inherited from the indirect base, B

    void f1(int) const; // error: D2 declared f2 as final

};

prevent dynamic binding of a call to a virtual function. We can use the scope operator to do so.

double undiscounted = baseP->Quote::net_price(42);

(e). Abstract Base Classes

  • Pure Virtual Functions: not have to be defined(可以不被define, 但不是必须不被define). specify that a virtual function by writing = 0 in place of function b. The = 0 只能是在declaration of virtual function in class body
    • 可以provide a definition for pure virtual. 但是function body 必须被定义outside the class.不能在provide function body inside the class that is =0
  • A class containing (or inheriting without overridding) a pure virtual function is an abstract base class
    • Abstract class 定义了interface for subsequent classes to override, 但不能create objects
  • 不能 create objects of a type that is an abstract base class.: abstract class 的constructor 是用于derived class的constructor 来 construct abstract class 的 member
  • refactoring: redesigning a class hierarchy to move operations and/or data from one class to another, 是common in OO application. 即使改变了继承体系, 那么使用 derived class代码无须改变, 但是必须recompile any code that uses those classes.

(f). Access Control and Inheritance

  • Like public, protected members are accessible to members and friends of classes derived from this class.
  • 在derived class 的derived class member中 access base protected members 可以通过derived objectaccess, 而不能通过 base-class objects.
  • derivation access specifier 不控制derived class members/functions 如何使用 direct base class. 而是控制 users of the derived class -including derived class的inherit class or objects—怎么使用direct base classes.
    • 比如 protected inherited, derived class的 member and friend 可以使用 base 的 public/protected, 但不能访问base 的private, 但是user 不能使用base 的public/protected
  • Whether the derived-to-base conversion 是否 accessible 取决于which code try to use conversion 和 access specifier

下面证明了: friend 定义在derived 中 access base member 的protected 只能用derived class object 而不能用base class object, 原因是friend 定义在derived 中 而不是定义在base 中. 如果可以access base class 的 protected member 通过base class object, 就即使不是friend 也有更改它的风险, 所以prevent such usage, 禁止这么做

class Base { 
protected:
    int prot_mem; // protected member
};
class Sneaky : public Base {
    friend void clobber(Sneaky&); // can access Sneaky::prot_mem 

    friend void clobber(Base&); // can't access Base::prot_mem 
    
    int j; // j is private by default

};
// ok: clobber can access the private and protected members in Sneaky objects 

void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }

// error: clobber can't access the protected members in Base

void clobber(Base &b) { b.prot_mem = 0; }

Accessibility of Derived-to-Base Conversion

Whether derived-to-base conversion accessible 取决于 access specifier. 假设D inherits from B B:

  • derived-to-base conversion : user can use only if D inherits public from B. 不accessible if D inherits from B using either protected or private.
  • Member functions and friends of D can use conversion to B regardless public, private, protected
  • Member functions and friends of classes derived from D can the conversion if D inherits from B is public or protected
  • Tip: 如果base class B 的public member 可以accessible, then derived-to-base conversion is also accessible

Friendship and Inheritance

  • friendship is not transitive, friendship is not inherited. Transitive 表示 B 是 A 的友元, C是B的友元, 但是 C 不能访问A. Inherited: 可以通过friend 的 inherited class 访问 base class member(下面例子). The access to Base objects 是embedded in object derived from Base
    • base class 的friends 没有access to members of its derived class
    • derived class 的 friends 没有acess to the base class
  • 当一个class(A) make another class(B) friend, 只能是another class (B) 访问 这个class (A), 两个friend 的 base class, derived class 都没有对彼此(A,B)的 special access
  • 如果想change access level of a name that derived class inherits. 可以这么做by providing a using declaration (using declaration accessibility 取决于 access specifier )
    • 比如using declaration is private, name is accessible to members and friends only
    • using declaration is public, name is available to all users of the class
    • using declaration is protect, name is available to memeber, friends, derived classes

下面例子 f3 看起来奇怪但是是okay 的, 因为用Sneaky object 访问的是 Base 的member, Palis a friend of Base.

class Base {
    // added friend declaration; other members as before

    friend class Pal; // Pal has no access to classes derived from Base

protected:
    int prot_mem;
};
class Sneaky: public Base{
    int j;
};
class Pal { 
public:
    int f(Base b) { return b.prot_mem; } // ok: Pal is a friend of Base

    int f2(Sneaky s) { return s.j; } // error: Pal not friend of Sneaky 
    
    // access to a base class is controlled by the base class, even inside a derived  object

    int f3(Sneaky s) { return s.prot_mem; } // ok: Pal is a friend 

};
// D2 has no access to protected or private members in Base 

class D2 : public Pal {
    public:
     int mem(Base b)
        { return b.prot_mem; } // error: friendship doesn't inherit

};

change access level: 下面例子 因为use private inheritance. 所以size and n 是 private members of Derived. The using declarations adjust the accessibility of these members.

class Base { 
public:
    std::size_t size() const { return n; } 
protected:
    std::size_t n;
};
class Derived : private Base { // note: private inheritance 

public:
    // maintain access levels for members related to the size of the object

    using Base::size; 
protected:
    using Base::n;
};

Default Inheritance Protection Levels

By default, a derived class defined with the class keyword has private inheritance; a derived class defined with struct has public inheritance:

  • 尽管class 是private by default, 但是最好explicit specify private(make it clear)
class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default 

class D2 : Base { /* ... */ }; // private inheritance by default

(g). Class Scope under Inheritance

  • 每一个class 都define 了自己的scope. 在inheritnace, scope of derived class nested inside scope of base classes.
    • 如果在当前class scope 内 a name unresolved, 会继续search for a definition in enclosing base-class scopes.
  • Name Lookup Happens at Compile Time: The static type of an object, reference, or pointer determines which members of that object are visible. 即使当dynamic types 出现时, 仍用static type determines what members can be used. 看下面例子
  • Name Collisions: a derived class can reuse(redefine) a name defined in direct or indirect base classes. Names defined in an inner scope (e.g., a derived class) hide name in the outer scope(base class)
    • use a hidden base-class member by using the scope operator: can overrides the normal lookup and directs the compiler to look for in the scope of class Base.
    • 除了override virtual, derived class 不应该reuse names defined in its base class.
    • 如果Derived class override function from base, 但也想要base all overloaded instances of that function available(而不是hide) in derived class cope, 可以提供 using declaration, specifies only a name, 不需要parameter list.

Name Lookup and Inheritance: Given call p->mem(), following 4 steps happen: Name lookup before Type Checking

  1. determine the static type of p, 因为我们call member, type 必须是class type
  2. Look for mem in class that correponds to static type of p. 如果mem 没有找到, look in direct class, chain of classes 直到 mem 被发现. 如果mem 没有被发现, call won’t compile
  3. 一旦mem 被找到, do the normal type checking to see if call is legal given the definition that was found
  4. 假设call is legal, compiler generate code, 取决于if call is virtual or not
    • 如果 mem is virtual and call is made through reference or pointer, compiler gnerates code to determine at run time which version to run 基于 dynamic type of the object
    • 如果 call is nonvirtual, or if the call is on object 而不是reference or pointer, compiler gnerates a normal function call, 即使是pointer 和 reference, 也是由static type 决定call 哪个versio
  5. 注意: Name lookup before Type Checking: 如果a member in a derived class (i.e., in an inner scope) has the same name as a base- class member(i.e., a name defined in an outer scope), derived member 会 hides base-class member 在derived class scope 内(即使function have different parameter lists is different for derived class and base class)
    • 但是可以用scope operator call base class version
    • 如果base 和 derived 的 virtual function 的parameter list 不同, no way to call derived version through a reference or pointer to base class

比如下面例子call isbn: 1. search isbn in Bulk_quote, not found 2. search isbnDisc_quote (Bulk_quote derived from Disc_quote), not found 3. search isbnQuote (Disc_quote derived from Quote), use of isbn is resolved

Bulk_quote bulk;
cout << bulk.isbn();

Static type determines what members can be used. 比如下面例子, 给Disc_quote 加了一个public member. (继承是Quote<-Disc_quote<- Bulk_quote) 尽管bulk确实含有discount_policy 对于itemP 却是看不见的, itemPQuote 的指针, 意味着对discount_policy 搜索从 Quote开始, 但是Quote 不包含名为 discount_policy 的成员。所以无法通过Quote 的对象, 引用,指针 call discount_policy

class Disc_quote : public Quote { 
public:
    std::pair<size_t, double> discount_policy() const { 
        return {quantity, discount}; 
    }
// other members as before 

};

Bulk_quote bulk;
Bulk_quote *bulkP = &bulk; // static and dynamic types are the same 

Quote *itemP = &bulk; // static and dynamic types differ 

bulkP->discount_policy(); // ok: bulkP has type Bulk_quote* 

itemP->discount_policy(); // error: itemP has type Quote*

Name Collision: Derived class mem hide 了 base class 的 mem

struct Base {
    Base(): mem(0) { }
protected:
    int mem;
};
struct Derived : Base {
    Derived(int i): mem(i) { } // initializes Derived::mem to i 
    
                                // Base::mem is default initialized

    int get_mem() { return mem; } // returns Derived::mem 

protected:
    int mem; // hides mem in the base 
    
};

Derived d(42);
cout << d.get_mem() << endl; // prints 42

可以use scope operator to use 被隐藏的base-class member, 比如 .The scope operator overrides the normal lookup and directs the compiler to look for mem starting in the scope of class Base.

struct Derived : Base {
    int get_base_mem() { return Base::mem; }
};

Name Lookup

struct Base {
    int memfcn(); 
};
struct Derived : Base {
    int memfcn(int);
    // hides memfcn in the base
    
};
Derived d; Base b;
b.memfcn(); // calls Base::memfcn

d.memfcn(10); // calls Derived::memfcn

d.memfcn(); // error: memfcn with no arguments is hidden d.Base::memfcn(); 

d.Base::memfcn();// ok: calls Base::memfcn

D1fcn 并没有覆盖 Base 的virtual (不是override, 而是hide fcn), 因为是parameter list 不同. D1 有两个函数fcn, 一个是从 Base 继承的virtual, 一个是自己定义nonvirtual and takes an int parameter.

class Base { 
public:
    virtual int fcn();
};
class D1 : public Base {
public:
    // hides fcn in the base; this fcn is not virtual

    // D1 inherits the definition of Base::fcn()

    int fcn(int); // parameter list differs from fcn in Base 
    
    virtual void f2(); // new virtual function that does not exist in Base

};

class D2 : public D1 { 
public:
    int fcn(int); // nonvirtual function hides D1::fcn(int) 
    
    int fcn(); // overrides virtual fcn from Base 
    
    void f2(); // overrides virtual f2 from D1
    
};
  • 前三条语句都是通过pointers to base class call 的. 因为 fcn 是virtual, compile run time 根据pointer bound的对象 决定哪个version to call. bp2 underlying object 是 D1. class 没有override fcn function that takes no argument. 所以call Base 的version
  • 接下来三条 calls are through pointers with differing types. bp2->f2(); is illegal 因为没有 static type of bp2 is Base, no f2() in Base.
  • 最后三组, dynamic type doesn’t matter when call a nonvirtual function. The version that is called depends only on the static type of the pointer.
Base bobj; D1 d1obj; D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; 

bp1->fcn(); // virtual call, will call Base::fcn at run time 

bp2->fcn(); // virtual call, will call Base::fcn at run time

bp3->fcn(); // virtual call, will call D2::fcn at run time


D1 *d1p = &d1obj; D2 *d2p = &d2obj;
bp2->f2(); // error: Base has no member named f2 

d1p->f2(); // virtual call, will call D1::f2() at run time

d2p->f2(); // virtual call, will call D2::f2() at run time


Base *p1 = &d2obj; D1 *p2 = &d2obj; D2 *p3 = &d2obj; 
p1->fcn(42); // error: Base has no version of fcn that takes an int 

p2->fcn(42); // statically bound, calls D1::fcn(int)

p3->fcn(42); // statically bound, calls D2::fcn(int)

下面例子okay的, 注意顺序,

  1. 声明Derived, 因为Base 中的friend 用到derived类
  2. 定义Base 类, 因为只有Base定义了, 才能作为Base Class 用于Derived class
  3. Derived Class 再 friend function 前, 因为friend function 用到了 derived function 的member
  4. 因为get 是Base friend, 所以可以access Base的中所有member, 即使是通过Derived class 来access的, 但是不可以通过Derived class 访问Derived 的member
class Derived;

class Base {
protected:
   int i = 10;
public:
   friend void get(Derived& d);
};
class Derived: public Base {
protected:
   int j = 5;
};

void get(Derived& d) {
   cout << " friend" << d.i << endl;
}
Derived i;
get(i);

(h). Constructors and Copy Control

1. Virtual Destructors

  • Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work.
  • destructor is run when we delete a pointer to a dynamically allocated object
  • 有可能 static type of pointer 与 dynamic types of pointer 指向 不同object 的情况(inheritance) . 需要run proper destructor by defining the destructor as virtual in the base class
  • Like any other virtual, the virtual nature of the destructor is inherited
  • Executing delete on a pointer to base that points to a derived object has undefined behavior if the base’s destructor is not virtual.
  • base class needs a virtual destructor还有个重要影响: 如果有destructor, even if use =default to use sythesize version, compiler 不会sythesize move operation (copy 是 deprecated) for that class
class Quote { 
public:
    // virtual destructor needed if a base pointer pointing to a derived object is deleted
    
    virtual ~Quote() = default; // dynamic binding for the destructor 

};

Quote *itemP = new Quote; // same static and dynamic type 

delete itemP; // destructor for Quote called

itemP = new Bulk_quote;  // static and dynamic types differ

delete itemP;  // destructor for Bulk_quote called

2.Synthesized Copy Control and Inheritance

  • constructor 顺序: base -> derived. Destructor 顺序(reverse order) : derived -> base. 当base-class constructor executing, derived part unititialized. 当base-class destructor run, derived part 已经被destoryed
    • 所以在base class destructor execute member, the object is incomplete, 因为derived part 已经被删除
    • To accommodate this incompleteness, the compiler treats the object type changes during construction or destruction. If a constructor or destructor calls a virtual, the version that is run is the one 与call 的 constructor/destructor 类型相同.
      • 在base constructor 里call virtual function, call base class version. 因为base class members 的还没有生成
  • derived class destructor 只负责clean 自己的, 不用负责clean base class (implicitly destoryed, base class 是virtual destructor).

copy control:

  • If the default constructor, copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor in the base class is deleted or inaccessible , then the corresponding member in the derived class is defined as deleted , because the compiler can’t use the base-class member to construct, assign, or destroy the base-class part of the object.
  • If the base class has an inaccessible or deleted destructor, then the synthesized default and copy/move constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object.

下面因为copy constructor defined, 不会生成move constructor for class B, 因为 B 不能copy or move

class B{
public:
    B();
    B(const B&) = delete;
    // other members, not including a move constructor

};
class D : public B {
};
D d; // ok: D's synthesized default constructor uses B's default constructor 

D d2(d); // error: D's synthesized copy constructor is deleted

D d3(std::move(d)); // error: implicitly uses D's deleted copy constructor

因为大多数base class 定义了 virtual destructor. 因此 base classes 不会生成 move operations (copy operations deprecated). 从而derived class 也不 synthesized.如果定义move, 也应该定义copy, 因为只定义move, copy 不会生成. 下面例子 Quote objects will be memberwise copied, moved, assigned, and destroyed. Moreover, classes derived from Quote will automatically obtain synthesized move operations as well

class Quote { 
public:
    Quote() = default; // memberwise default initialize 
    
    Quote(const Quote&) = default; // memberwise copy 
    
    Quote(Quote&&) = default; // memberwise copy

    Quote& operator=(const Quote&) = default; // copy assign Quote& 
    
    operator=(Quote&&) = default; // move assign 
    
    virtual ~Quote() = default;
    // other members as before 

};

**Derived-Class Destructor**

```c++
class D: public Base { 
public:
    // Base::~Base invoked automatically
    
    ~D() { /* do what it takes to clean up derived members*/ }
};

3. Derived-Class Copy/Move constructor

  • When a derived class defines a copy or move operation, that operation is responsible for copying or moving the entire object, including base-class members
    • derived synthesized copy constructor 会自动call base (synthesized or defined) constructor
    • derived defined copy constructor 但没有call base copy constructor, 会call base default constructor,因为没有explicitly 指出, 所以B 的copy constructor 会先initialize base member using base default constructor.
    • derived defined copy constructor call base copy constructor, explicitly call base copy constructor in derived’s copy constructor initializer list 这样initialize base member using base copy constructor.
    • 如果self-define assignment operator in derived class, 同理必须explicitly call copy / move assignment operator in derived class copy/move assignment operator
//derived synthesized copy constructor

class A {
public:
    A() {
        std::cout << "A::Default constructor" << std::endl;
    }
    A(const A& rhs) {
        std::cout << "A::Copy constructor" << std::endl;
    }
};
class B : public A {
public:
    B() {
        std::cout << "B::Default constructor" << std::endl;
    }
};

std::cout << "Creating B" << std::endl;
B b1;
std::cout << "Creating B by copy" << std::endl;
B b2(b1);
/*
Creating B

A::Default constructor

B::Default constructor

Creating B by copy

A::Copy constructor
*/
class A {
public:
    A() {
        std::cout << "A::Default constructor" << std::endl;
    }
    A(const A& rhs) {
        std::cout << "A::Copy constructor" << std::endl;
    }
};
class B : public A {
public:
    B() {
        std::cout << "B::Default constructor" << std::endl;
    }
    B(const B& rhs) {
        std::cout << "B::Copy constructor" << std::endl;
    }
};
std::cout << "Creating B" << std::endl;
B b1;
std::cout << "Creating B by copy" << std::endl;
B b2(b1);
return 0;
/*
Creating B

A::Default constructor

B::Default constructor

Creating B by copy

A::Default constructor

B::Copy constructor
*/
class A {
public:
    A() {
        std::cout << "A::Default constructor" << std::endl;
    }
    A(const A& rhs) {
        std::cout << "A::Copy constructor" << std::endl;
    }
};

class B : public A {
public:
    B() {
        std::cout << "B::Default constructor" << std::endl;
    }
    B(const B& rhs):A(rhs) {
        std::cout << "B::Copy constructor" << std::endl;
    }
};

std::cout << "Creating B" << std::endl;
B b1;
std::cout << "Creating B by copy" << std::endl;
B b2(b1);
/*
Creating B

A::Default constructor

B::Default constructor

Creating B by copy

A::Copy constructor

B::Copy constructor
*/

注意下面 Base(d) pass D object to base-class constructor 是 derived-to-base conversion. 因为base class copy constructor 接受是 const reference and copy the base part of d. 假如copy constructor 忽略 base initializer Base(d), 那么Base 的 default constructor 用于 initialize base part.

class Base{/* ... */}; 
class D: public Base { 
public:
    // by default, the base class default constructor initializes the base part of an object 
    
    // to use the copy or move constructor, we must explicitly call that

    // constructor in the constructor initializer list

    D(const D& d): Base(d) // copy the base members

    /* initializers for members of D */ { /* ... */ }
    
    D(D&& d): Base(std::move(d)) // move the base members

    /* initializers for members of D */ { /* ... */ }
 };

D 的assignment operator, 注意因为是self-define assignment operator 并不会自动call base class synthesized/self-define assignment operator,

// Base::operator=(const Base&) is not invoked automatically 

D &D::operator=(const D &rhs)
{
    Base::operator=(rhs); // assigns the base part

    // assign the members in the derived class, as usual,

    // handling self-assignment and freeing existing resources as appropriate 
    
    return *this;
}

(i). Inherited Constructors:

  • Class *可以继承base constructor, 但 cannot inherit the default, copy, and move constructors. If the derived class does not directly define these constructors, the compiler synthesizes them
    • inherit base-class constructor by providing a using declaraction that names its (direct) base class
    • 通常情况下using declaration only makes a name visible in the current scope. When applied to a constructor, a using declaration causes the compiler to generate code. 因此对于每一个constructor in base, compiler generates a constructor in derived that has the same parameter list
      • These compiler-generated constructors have the form derived(parms) : base(args) { }
      • 如果derived class 有任何data members of its own, 都default initialized
class Bulk_quote : public Disc_quote { 
    public:
    using Disc_quote::Disc_quote; // inherit Disc_quote's constructors 
    
    double net_price(std::size_t) const;
};

//using Disc_quote::Disc_quote 等同于

Bulk_quote(const std::string& book, double price, std::size_t qty, double disc):
    Disc_quote(book, price, qty, disc) { }

Characteristics of an Inherited Constructor

  • Unlike using declarations for ordinary members, a constructor using declaration does not change the access level of the inherited constructor(s). 比如regardless of where the using declaration, base 的private constructor 在derived 还是private, base 的 protected constructor 在 derived 还是 protected
  • a using declaration can’t specify explicit or constexpr. If a constructor in the base is explicit or constexpr , the inherited constructor has the same property
  • If a base-class constructor has default arguments, those arguments are not inherited. Instead, the derived class gets multiple inherited constructors in which each parameter with a default argument is successively omitted.
    • 比如base class 有一个constructor with two parameters, second has default-value, 在derived class 将会有两个constructors, 一个with both parameters (第二个没有default), 另一个只有一个parameters(left-most, non-defaulted parameter)
  • derived class 会继承大多数base constructor,但有两个例外
    • 例外一: derived class defines a constructor with the same parameters as base class, base 这些constructor 不会继承, derived defined constructor 会 代替 base 的
    • 例外二: default, copy, and move constructor 不会被inherited. 这些函数不被继承, 而按照规则被synthesize
  • Inherited constructor 不被 treat as user-defined constructor, 因为class 只包括了 inherited constructor 会有 synthesized default constructor

(j). Containers and Inheritance

不能push object in vector, 比如Quote 是 Bulk_quote 的base, 让 vectorQuote, 但是当push Bulk_quote 进vector, vector 存放的就不在是 Bulk_quote object, 因为进行了 derived-to-base conversion(在copy constructor, not pointer/reference) and derived part is ignored

vector<Quote> basket; 
basket.push_back(Quote("0-201-82470-1", 50));
// ok, but copies only the Quote part of the object into basket 

basket.push_back(Bulk_quote("0-201-54848-8", 50, 10, .25)); 

// calls version defined by Quote, prints 750, i.e., 15 * $50

cout << basket.back().net_price(15) << endl;
  • Put (Smart) Pointers, Not Objects, in Containers: When we need a container that holds objects related by inheritance, we typically define the container to hold pointers (preferably smart pointers) to base class.

下面例子中的basket.back()->net_price 取决于dynamic type 的version. Derived-to-Base conversion 也适用于 smart pointer, we can also convert a smart pointer to a derived type to a smart pointer to an base-class type. make_shared<Bulk_quote> returns a shared_ptr<Bulk_quote> object, which is converted to shared_ptr<Quote>

vector<shared_ptr<Quote>> basket; 
basket.push_back(make_shared<Quote>("0-201-82470-1", 50)); 
basket.push_back(
    make_shared<Bulk_quote>("0-201-54848-8", 50, 10, .25)); 
// calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount 

cout << basket.back()->net_price(15) << endl;

Basket Class

  • 使用multiset, no less-than operator for shared_ptr, 因此必须provide our own comparison operator to order elements, 定义了个private static member, compared isbn
  • upper_boundmultiset 中用的很巧妙, 跳过相同的所有元素
  • print_total 调用了 virtual call to net_price, resulting price 取决于dynamic type of **iter
  • Users of Basket still have to deal with dynamic memory,因为 add_item takes a shared_ptr
    • 可以redefine add_item to take a Quote object instead of shared_ptr.
class Basket { 
public:
    //Basket uses synthesized default constructor and copy-control members

    void add_item(const std::shared_ptr<Quote> &sale) { items.insert(sale); }

    // prints the total price for each book and the overall total for all items in the basket

    double total_receipt(std::ostream&) const; 

private:
    // function to compare shared_ptrs needed by the multiset member

    static bool compare(const std::shared_ptr<Quote> &lhs,
        const std::shared_ptr<Quote> &rhs) 
    { return lhs->isbn() < rhs->isbn(); }
    // multiset to hold multiple quotes, ordered by the compare member

    std::multiset<std::shared_ptr<Quote>, decltype(compare)*>items(compare);
};


double Basket::total_receipt(ostream &os) const {
    double sum = 0.0;
    // iter refers to the first element in a batch of elements with the same ISBN

    // upper_bound returns an iterator to the element just past the end of that batch 

    for (auto iter = items.cbegin(); 
            iter!=items.cend();
            iter = items.upper_bound(*iter)) {
            // we know there's at least one element with this key in the Basket 
            
            // print the line item for this book

            sum += print_total(os, **iter, items.count(*iter)); 
            //*iter, shared_ptr, **iter is Quote object in shared_ptr

    }    
    os << "Total Sale: " << sum << endl; // print the final overall total

    return sum;

}

//user 

Basket bsk;
bsk.add_item(make_shared<Quote>("123", 45)); 
bsk.add_item(make_shared<Bulk_quote>("345", 45, 3, .15));

重新定义 add_item, 但可能不正确, 比如下面定义的, somewhere there will be a new expression 例如 new Quote(sale). 不幸的是, expression won’t do right thing: 因为sale 可能不是 Quote, 而是Derived class Bulk_quote the object will be sliced down.

void add_item(const Quote& sale); // copy the given object 

void add_item(Quote&& sale); // move the given object

Clone Function

  • 解决上面的问题可以定义 clone function
  • we have a copy and a move version of add_item, we defined lvalue and rvalue versions of clone. lvalue reference member copies itself into that newly allocated object; rvalue reference member moves its own data.
  • Like add_item, clone is overloaded based on whether its is called on lvalue or rvalue
    • 需要注意的是 rvalue verson 的 salervalue reference, 但sale is lvalue. 所以可以call move to bind an rvalue reference to sale
  • clone function is vritual. Whether Quote or Bulk_quote function is run, 取决于 dynamic type of sale. 无论是copy or move, clone 返回a pointer to newly allocated object. 因为shared_ptr support derived-to-base conversion, we can bind a shared_ptr<Quote> to a Bulk_quote*
class Quote { 
public:
    // virtual function to return a dynamically allocated copy of itself 
    
    // these members use reference qualifiers
    
    virtual Quote* clone() const & {return new Quote(*this);}
    virtual Quote* clone() && Quote(std::move(*this));}
    // other members as before

};
class Bulk_quote : public Quote {
    Bulk_quote* clone() const & {return new Bulk_quote(*this);}
    Bulk_quote* clone() &&  { return Bulk_quote(std::move(*this));}
// other members as before 

};

class Basket { 
public:
    void add_item(const Quote& sale) // copy the given object

    { items.insert(std::shared_ptr<Quote>(sale.clone())); }
    
    void add_item(Quote&& sale) // move the given object 
    
    { items.insert(
        std::shared_ptr<Quote>(std::move(sale).clone())); } // other members as before

};

15.9. Text Queries Revisited

把之前的shared_ptr 做了下更改加上了get_file function 和 begin, end iterator

//顺序 先run TextQuery query 然后再print result 

ifstream is("file.txt");
TextQuery tq(is);
QueryResult qr = tq.query("has");
print(cout, qr);

class QueryResult
{
     friend ostream& print(ostream&, const QueryResult&);
public:
     using line_no = vector<string>::size_type;
     QueryResult(string s, shared_ptr<set<line_no>>p, shared_ptr<vector<string>>f) :
	 sought(s), lines(p), file(f) {}
     shared_ptr<vector<string>>get_file() const {
	return file;
     }
     set<line_no>::const_iterator cbegin() const { return lines->cbegin();}
     set<line_no>::iterator begin() { return lines->begin(); }
     set<line_no>::const_iterator cend() const { return lines->cend(); }
     set<line_no>::iterator end() { return lines->end(); }
     string sought;
     shared_ptr<set<line_no>>lines;
     shared_ptr<vector<string>>file;
};

string make_plural(size_t ctr, const string& word, const string& ending) {
     return ctr > 1 ? word : word + ending;
}


ostream& print(ostream& os, const QueryResult& qr) {
     os << qr.sought << " occurs " << qr.lines->size()
	<< " " << make_plural(qr.lines->size(), "time", "s") << endl;

     for (auto num : *qr.lines) 
	 os << "(line " << num + 1 << " " << (*qr.file)[num] << endl;
     return os;
}

class TextQuery
{
public:
     TextQuery(ifstream&);
     QueryResult query(const string&) const;
private:
     using line_no = vector<string>::size_type;
     shared_ptr<vector<string>>file;
     map <string, shared_ptr<set<line_no>>>wm;
};

TextQuery::TextQuery(ifstream& is):file(new vector<string>) {
    string text;
    while (getline(is,text)) {
	int n = file->size();
	file->push_back(text);
	istringstream line(text);
	string word;
	while (line >> word) {
	     auto& lines = wm[word]; 
	     if (!lines) 
	          lines.reset(new set<line_no>);     
	     lines->insert(n); 
	}
    }
}

QueryResult TextQuery::query(const string& sought) const {
     static shared_ptr<set<line_no>>empty = make_shared<set<line_no>>();
     auto loc = wm.find(sought);
     if (loc == wm.end())
	return QueryResult(sought, empty, file);
     return QueryResult(sought, loc->second, file);
}

现在像改进上面方面让他们work for some operator 比如 &, |, ~, 比如下面例子

1 has 
2 not
3 has
5 not 
6 we do

run Query q = Query("1") & Query("has") | ~Query("not");返回

((1 & has) | ~(not)) occurs 3 time 
(line 1) 1 has 
(line 3) 3 has 
(line 6) 6 we do 

通过定义operator overloading, 我们想让有这样的structure

			
		Query_base 
		/    |      \
               /     |        \
	WordQuery   NotQuery   BinaryQuery
			       /   \
			      /     \
			 AndQuery    orQuery 

Query是user call, 如果call Query时

  • 没有operator, 会initialze WordQuery 作为QueryBase(Query 的member), call eval 时 直接用t.query(单个word), t是 TextQuery 类
  • 如果周围有operator, 会右元的operator 中定义是 初始化AndQuery, OrQuery, or NotQuery 作为QueryBase(Query 的member)

因为Query 是QueryBase friend, 所以当最后eval时,call QueryBase 的private virtual 找 dynamic type(WordQuery? AndQuery? OrQuery? NotQuery?) 真正的virtual runtime version

//call 下面code 的方法是:

ifstream is("file.txt");
TextQuery tq(is);
Query q = Query("1") & Query("has") | ~Query("not");
QueryResult qr = q.eval(tq);
print(cout, qr);


//因为不能instanitate class, all members non public

//member of Query will call Query_base

//定义virtual private 是因为 call virtual 是右元 friend call, private 可以被access

class Query_base
{
     friend class Query;
protected:
     using line_no = TextQuery::line_no;
     virtual ~Query_base() = default;
private:
     virtual QueryResult eval(const TextQuery&) const = 0;
     virtual std::string rep() const = 0;
};


//private constructor: 因为operator 是 friend, 可以access 

class Query {
     friend Query operator~(const Query&);
     friend Query operator|(const Query&, const Query&);
     friend Query operator&(const Query&, const Query&);
public:
     Query(const string&);
     QueryResult eval(const TextQuery& t) const {
          return q->eval(t);
     }
     std::string rep() const {
          return q->rep();
     }
private:
     Query(std::shared_ptr<Query_base>query) : q(query) {}//可以有type conversion
     
     shared_ptr<Query_base>q;
};

std::ostream& operator<< (ostream& os, const Query& query) {
     os << query.rep();
     return os;
}


class WordQuery :public Query_base {
     friend class Query;
     WordQuery(const string& s) : query_word(s) {}
     QueryResult eval(const TextQuery& t) const override {
          return t.query(query_word);
     };
     std::string rep() const override {
          return query_word;
     }
     string query_word;
};


inline Query::Query(const std::string& s) : q(new WordQuery(s)) {}

class NotQuery : public Query_base {
     friend Query operator~(const Query&);
     NotQuery(const Query& q) : query(q) {}
     string rep() const { return "~(" + query.rep() + ")"; }
     QueryResult eval(const TextQuery& t)const override;
     Query query;
};

inline Query operator~(const Query& operand) {
     return shared_ptr<Query_base>(new NotQuery(operand));
}

//shared_ptr<Query_base>(new NotQuery(operand)); 等同于

//allocate a new NotQuery objet and 

//binding the result NotQuery pointer to a shared_ptr<Query_base>

//shared_ptr<Query_base> tmp ( new NotQuery(expr)); 

// return Query(tmp);


//注意: BinaryQuery 没有define eval 继承 pure Virtual. BinaryQuery是abstract class

class BinaryQuery : public Query_base {
protected:
     BinaryQuery(const Query& l, const Query& r, string s) :
          lhs(l), rhs(r), opSym(s) {}
     string rep() const override {
          return "(" + lhs.rep() + " " + opSym + "" + rhs.rep() + ")";
     }
     Query lhs, rhs;
     string opSym;
};



//AndQuery 和 OrQuery 继承 BinaryQuery的rep, 但是overrides the eval function

class AndQuery :public BinaryQuery {
     friend Query operator& (const Query&, const Query&);
     AndQuery(const Query& left, const Query& right) :
     BinaryQuery(left, right, "&") {}
     QueryResult eval(const TextQuery&)const override;
};

inline Query operator&(const Query& lhs, const Query& rhs) {
     return shared_ptr<Query_base>(new AndQuery(lhs, rhs));
}

class OrQuery : public BinaryQuery {
     friend Query operator| (const Query&, const Query&);
     OrQuery(const Query& left, const Query& right) :
          BinaryQuery(left, right, "|") {}
     QueryResult eval(const TextQuery&) const override;
};

inline Query operator|(const Query& lhs, const Query& rhs) {
      return shared_ptr<Query_base>(new OrQuery(lhs, rhs));
}


//Or eval function, union

//因为lhs,rhs 都有一样file, get_file 来自于哪个都可以的

QueryResult  OrQuery::eval(const TextQuery& text) const {
     QueryResult right = rhs.eval(text), left = lhs.eval(text);
     auto ret_lines = make_shared<set<line_no>>(left.cbegin(), left.cend());
     ret_lines->insert(right.cbegin(), right.cend());
     return QueryResult(rep(), ret_lines, left.get_file());
}


//And eval function, interesection
QueryResult AndQuery::eval(const TextQuery& text) const {
     QueryResult right = rhs.eval(text), left = lhs.eval(text);
     auto ret_lines = make_shared<set<line_no>>(); 
     //不可以写 shared_ptr<set<lines_no>> ret_lines 否则是未初始化的pointer
     
     
     set_intersection(left.cbegin(), left.cend(), right.cbegin(), right.cend(),
		inserter(*ret_lines, ret_lines->begin()));
     shared_ptr<set<line_no>> rhs_ret_lines = 
     		make_shared<set<line_no>>(right.cbegin(), right.cend());
     return QueryResult(rep(), ret_lines, left.get_file());
}

//Not eval function,
QueryResult NotQuery::eval(const TextQuery& text)const {
     QueryResult result = query.eval(text);
     auto ret_lines = make_shared<set<line_no>>();
     auto it = result.cbegin(), end = result.cend();
     for (size_t i = 0; i < result.get_file()->size(); i++) {
          if (it != end && i == *it) ++it;
	  else ret_lines->insert(i);
     }
     return QueryResult(rep(), ret_lines, result.get_file());
}





16. Templates and Generic Programming

(a). Function Templates

  • 比如overload functions and only parameter differs. 有多个repeat function body is error-prone
    • 比如compare function 可以overload很多个built-in type 版本, 但是对于compare user-defined type(class) 无法work, 因为不知道 user 怎么定义class

A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens.

  • Compare function 是 reference to const. 确保types 不能被copied. 许多types, 包括built-in types 除了 unique_ptrIO types 都可以copies. 不allow copy, make is faster for large objects
  • 注: passing in 的 type只需要定义 < operator 既可以, 无需提供 >, 如果concerned type independence and portability, 应该定义function using less , 原来版本缺陷是, 如果定义pointers. 如果两个pointers not point to the array, code is undefined
template<typename T>
int compare(const T& v1, const T& v2)
{
    if (v1 < v2) return -1;
    if(v2  < v1) return 1;
    return 0;
}

// version of compare that will be correct even if used on pointers; see § 14.8.2 (p. 575)

template <typename T> int compare(const T &v1, const T &v2)
{
    if (less<T>()(v1, v2)) return -1; 
    if (less<T>()(v2, v1)) return 1; 
    return 0;
}
  • In a template definition, the template parameter list cannot be empty.
    • When use a template, 需要 specify either implicitly or explicitly - template arguments to bind to the template parameter(s).
    • use name T to refer to a type. 实际type T determined at compile time based on how compare is used.
  • 当 function template, compiler uses the arguments of the call to deduce template arguments. 比如下面例子argument 是 int. The compiler deduce int as template argument and bind that to template parameter T.
    • compiler uses the deduced template parameter(s) to instantiate a specific version of function. 会用actual template arguments 代替 corresponding template parameter(s)
  • type parameter: 上面例子中的 T. Type parameter 可以用作 return type or function parameter type, and for varaible declarations or casts inside the function
    • 每一个type parameter 前面必须跟一个keyword class or typename
  • Nontype Template Parameters: can define templates that nontype parameters. A nontype parameter represents a value rather than a type.
    • Nontype parameter 是 specified by type name 而不是 keyword class or typename
    • An argument bound to a nontype parameter 必须是constant expression. Arguments bound to a pointer or reference nontype parameter must have static lifetime(静态生存期, 在stack上, 而不是heap) 所以不能是local(nonstatic) object or a dynamic object as nontype template argument 来作为reference or pointer 的参数. A pointer parameter can also be instantiated by nullptr or a zero-valued constant expression
    • 当template is instantiated, nontype parameters are replaced with a value supplied by the user or deduced by the compiler. Values 必须是 constant expression, 允许compiler to instantiate the templates during compile time.
  • function template 可以被 declared inline or constexpr defined same as nontemplate function. inlineconstexpr 需要在template paramerter list 之后, return type 之前
  • Template programs should try to minimize the number of requirements placed on the argument types. (比如定义template 不能接受pointer 就是 minimize requirement of argument types)

Compiler 使用argument to deduce the type, 比如下面

// instantiates int compare(const int&, const int&)

cout << compare(1, 0) << endl; // T is int

vector<int> vec1{1, 2, 3}, vec2{4, 5, 6};
// instantiates int compare(const vector<int>&, const vector<int>&)

cout << compare(vec1, vec2) << endl; // T is vector<int>

type parameter

// ok: same type used for the return type and parameter 

template <typename T> T foo(T* p)
{
T tmp = *p; // tmp will have the type to which p points 

return tmp;
}

// error: must precede U with either typename or class

template <typename T, U> T calc(const T&, const U&);

nontype parameter

比如compare string literals. Literals are arrays of const char. 我们不能copy an array, we’ll define our parameters as references to array. 我们想compare literals of different lengths, we give our template 两个 nontype parameters. 因为first template parameter 表示 size of first array, and second parameter 表示 size of second array. Compiler will use size of literals to instantiate a version of template with size 代替 N and M . 记住compiler 插入一个 null terminator at the end of string literal

template<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
//call compare

compare("hi", "mom")
//等同于

int compare(const char (&p1)[3], const char (&p2)[4])

inline and constexpr Function Templates

// ok: inline specifier follows the template parameter list

template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of the inline specifier

inline template <typename T> T min(const T&, const T&);

(b). Template Compilation

  • When the compiler sees the definition of a template, it does not generate code. generates code only when we instantiate a specific instance of the template.
    • when use template 才generate code 而不是define 时候, 这个affects how we organize our source code and when errors are detected
  • 通常上call function, compiler 只需看见declaration for the function. 同样当使用 objects of class type, class definition 必须 available, 但是definitions of member functions 不需要present, 因此defintions and function declarations in header files and class-member functions 的definition in source files.
    • 但是template are different, To generate an instantiation, the compiler needs to have the code that defines a function template or class template member function. As a result, unlike nontemplate code, Definitions of function templates and member functions of class templates are ordinarily put into header files.

Key Concept: Templates and Headers

  • Templates contain two kinds of names:
    • Those that do not depend on a template parameter
    • Those that do depend on a template parameter
  • 是template 作者保证 names not depend on template parameter 必须visible when template is used.
  • 此外, template 作者 必须保证 when template is instantiated, definition of template including definitions of members of class template are visible
  • user 保证 declarations for all functions, types and operators associated with they types(template parameter) used to instantiate the template 是 visible
  • 为满足上面requirement,
    • Authors of templates should provide a header that contains the template definition along with declarations for all the names used in the class template or in the definitions of its members
    • Users of the template must include the header for the template and for any types used to instantiate that template.
    • Users of the template 需要保证arguments passed to the template support any operations that template uses 以及这些operations 可以在template 中正确工作..

Compilation Errors Are Mostly Reported during Instantiation

  • 通常compiler有三个stages 报告错误
    1. 第一阶段是 when compile the template itself. Compiler 不会find many errors at this stage. compiler 可能detect syntax errors 比如忘记semicolon or 错误拼写variable name
    2. 第二个阶段是 when the compiler sees a use of the template. At this stage, there is still not much the compiler can check.
      • For a call to a function template: check argument 数量是不是合适, check argument 是不是类型匹配
      • For a class template, the compiler check 是否提供了 正确数量的 template arguments
    3. 第三阶段是 during instantiation. 只有这个阶段才能发现 type related error, Depending on how the compiler manages instantiation, these errors may be reported at link time. 但是template code 通常会做一些 被用到的types 假设

比如下面. template 会做一些 被用到 type 假设, 比如下面假设 argument type 有 < operator. 当compiler 处理 body of template, 不能verify 首付 condition in if 是 legal. 如果argument pass to compare 定义了 < 运算符, 代码是正确的, 否则是错误的. 比如Sales_data 没有定义 < operator. 这个错误直到 instantiates the definition of compare on type Sales_data才被发现.

if (v1 < v2) return -1; // requires < on objects of type T 

if (v2 < v1) return 1; // requires < on objects of type T 

return 0; // returns int; not dependent on T

Sales_data data1, data2;
cout << compare(data1, data2) << endl; // error: no < on Sales_data

(c). Class Template

  • 不同于function template, compiler cannot deduce class template parameter types
    • user must specify element type is a list of explicit template arguments that are bound to the template’s parameters. Compiler 用这些 template arguments to instantiate specific class from template.
      • 当compile instantiates a class from template, 会 rewrites template, replacing each instance of the template parameter T by the given template argument
      • 用一个template, template argument不同, 会instantiate 不同的distinct classes. Each instantiation of a class template constitutes an independent class. The type Blob<string> has no relationship to, or any special access to, the members of any other Blob type.
  • class template name 不是type, 当class template 被实例化, type always includes template argument(s).
  • member function:
    • member defined inside the template class body are implicitly inline
    • 每个instantiation of class has its own version of each member. 因此template class function 跟他们的template class itself 都有一样的template parameters. 所以在class 外面定义function, 需要用 keyword template followed by class’ template parameter list
    • By default, a member of an instantiated class template is instantiated only if the member is used. member 再被用的时候才被初始化. 如果function 不被用到,, it is not instantiated
      • 这使得也许某一type 不 meet requirement for some template’s operations. 我们也可以instantiate a class with that type (比如, template 有的function 需要这个type, 有+ overloading, 但是没有用到这个template function, 所以没关系)
    • 一个例外是: Inside the scope of a class template, we may refer to the template without specifying template argument(s)., 比如return type (value or reference, or inside template class function). 因为在class scope, compiler treats references to the template itself 就像提供了template arguments 一样.
    • 但是在template 外面定义时候, 记得return type is not in class scope, 需要提供template argument. we are not in the scope of the class until the class name is seen
  • template class 的声明 template<typename> class Blob 不用加T, 也可以加T template<typename T> class Blob
  • template operator 的定义 template<type T> bool operator==<T>(const Blob<T>&, const Blob<T>& ); 只能用于T 类的比较

e.g.

template<typename T>class Blob{
public: 
    typedef T value_type;
    typedef typename vector<T>::size_type size_type;
    //constructors

    Blob();
    Blob(std::initializer_list<T>il);
    size_type size () const {return data->size();}
    bool empty() const {return data->empty();}
    //add and remove elements

    void push_back(const T & t) {data->push_back(t);}
    void push_back(const T &&t) {data->push_back(std::move(t));}
    void pop_back();
    T& back();
    T& operator[] (size_type i);
private: 
    std::shared_ptr<std::vector<T>>data;
    void check(size_type i, const std::string& msg) const;
};

Blob<int> ia; // empty 

Blob<int> Blob<int> ia2 = {0,1,2,3,4}; // Blob<int> with five elements

定义Blob<int> 等同于 把所有T 都换成 int

template<> class Blob<int>{
    typedef typename std::vector<int>::size_type size_type; Blob();
    Blob(std::initializer_list<int> il);
    // ...

    int& operator[](size_type i);
private:
    std::shared_ptr<std::vector<int>> data;
    void check(size_type i, const std::string &msg) const;
};

不同的template argument 生成不同的class, 比如下面code would trigger instantiations of two distinct classes.

Blob<string> names; // Blob that holds strings 

Blob<double> prices;// different element type

定义template class member outside class: 用keyword template + template parameter, 因为每个template class instantiation 都有自己version of class. General form 如下

template <typename T>
ret-type Blob<T>::member-name(parm-list)

Blob function 定义 其中 subscript operator 和 back 还可以overload const version. constructor 有default constructor 和 接受initializer_list

template<typename T>
void Blob<T>::check(size_type i, const std::string &msg) const
{
    if (i >= data->size())
        throw std::out_of_range(msg);
}
template <typename T> T& Blob<T>::back()
{
    check(0, "back on empty Blob");
    return data->back();
}
template <typename T>
T& Blob<T>::operator[](size_type i)
{
    check(i, "subscript out of range"); 
    return (*data)[i];
}
template <typename T> void Blob<T>::pop_back()
{
    check(0, "pop_back on empty Blob");
    data->pop_back();
}
template <typename T>
Blob<T>::Blob(): data(std::make_shared<std::vector<T>>()) { }
template <typename T> Blob<T>::Blob(std::initializer_list<T> il):
data(std::make_shared<std::vector<T>>(il)) { }

Template member function 被实例化 只当 程序use that member function

//instantiates Blob<int> and the initializer_list<int> constructor

Blob<int> squares = {0,1,2,3,4,5,6,7,8,9}; 

// instantiates Blob<int>::size() const

for (size_t i = 0; i != squares.size(); ++i)
    squares[i] = i*i; // instantiates Blob<int>::operator[](size_t)

one exception to the rule that we must supply template arguments when we use a class template type. Inside the scope of the class template itself, we may use the name of the template without arguments. 注意return type of prefix increment/decrement 返回 BlobPtrBlobPtr&, 而不是 BlobPtr<T>&.

// BlobPtr throws an exception on attempts to access a nonexistent element 

template <typename T> class BlobPtr{
public:
    BlobPtr(): curr(0) { } 
    BlobPtr(Blob<T> &a, size_t sz = 0):
        wptr(a.data), curr(sz) { } 
    T& operator*() const
    { 
        auto p = check(curr, "dereference past end");
        return (*p)[curr]; // (*p) is the vector to which this object points
    } 
    // increment and decrement

    BlobPtr& operator++(); // prefix operators 
    
    BlobPtr& operator--();
private:
    // check returns a shared_ptr to the vector if the check succeeds 
    
    std::shared_ptr<std::vector<T>>check(std::size_t, const std::string&) const;

    // store a weak_ptr, which means the underlying vector might be destroyed 
    
    std::weak_ptr<std::vector<T>> wptr;
    std::size_t curr; // current position within the array

};

//compiler treats references to the template itself 

//就像提供了template arguments 一样

BlobPtr& operator++(); 
BlobPtr& operator--();
//等同于

BlobPtr<T>& operator++(); 
BlobPtr<T>& operator--();

值得注意的是,return type 是outside scope of the class, 必须 specify that the return with the same type(template argument) as the class. 在function body, in scope of class and do not repeat template argument (当define ret), compiler 会assumes we are using the same type as member’s instantiation.

// postfix: increment/decrement the object but return the unchanged value 

template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int)
{
    // no check needed here; the call to prefix increment will do the check 
    
    BlobPtr ret = *this; // save the current value

    ++*this; // advance one element; prefix ++ checks the increment 
    
    return ret; // return the saved state

}

(d). Friends

  • 当一个class 含有 friend declaration, class and friends 可以两个都是template, 一个是,或一个都不是.
    • class template 有一个 nontemplate friend 保证了 friend access to all the instantiation of the class template
    • class template 有一个 template friend, 可以给all friend template 的class template access 或给 特定的 friend template 的class template access
  • class -> template class friend (如果对应的friend template class 是唯一的关系, 需要forward declaration)
    • 一对一关系: 用class 实例化template class. ,Template friend class 需要 forward declaration, class C; friend class Pal2<C>;
    • 一对多关系, 所有实例(instance)都是class friend. template class friend不需要foward declaration class C; template<typename X> class Pal2;
  • template class -> class friend
    • 多对一关系: friend class 类不用forward declaration, 比如 template <typename T> class C2; 中声明 friend class Pal3;
  • template class -> template class friend,
    • 一对一关系,每个实例 将相同实例化 声明为friend: Template friend 需要 forward declaration. e.g. template <typename T> class Pal; 中声明 friend class Pal2<T>;
    • 多对多关系, 所有的实例都是 友元class 实例的 friend, template class friend不需要foward declaration, 比如 template <typename T> class C2; 中声明 template <typename X> friend class Pal2;

Rule: 如果friend declare是 template <typename X> friend 无需forward declaration, 如果是friend class C<T> 需要forward declaration

One-to-One Friendship (只能是同一个type 之间的friendship)

最常见的friendship between 一个class template 和 其他的template (可以是class or function) 是建立在 friendship between instantiations of the class and its friends. 比如下面例子 Blob 有 friend BlobPtrBlob equlity operator

下面例子,

  • 需要先 声明 BlobPtrBlob , 这些是 operator == 函数参数声明以及 Blob 友元声明需要的.
  • Blob friend declaratons use Blob’s template parameter as their own template argument.
  • 因此friendship 限定在相同type 实例化 的 Blob 和 BlobPtr 相等运算运算之前. members of BlobPtr<char> 可以access nonpublic parts of Blob<char>, 但是 Blob<char>Blob<int> 没有special access 或者 其他instantiation of Blob
// forward declarations needed for friend declarations in Blob

template <typename> class BlobPtr;
template <typename> class Blob; // needed for parameters in operator== 

template <typename T>
    bool operator==(const Blob<T>&, const Blob<T>&); 

template <typename T> 
class Blob {
    // each instantiation of Blob grants access to the version of

    // BlobPtr and the equality operator instantiated with the same type
    
    friend class BlobPtr<T>; 
    friend bool operator==<T>
    	(const Blob<T>&, const Blob<T>&); 
    // other members as in § 12.1.1 

};

Blob<char> ca; // BlobPtr<char> and operator==<char> are friends 

Blob<int> ia; // BlobPtr<int> and operator==<int> are friends

For non template class, no need forward declaration

class Blob {
    // each instantiation of Blob grants access to the version of

    // BlobPtr and the equality operator instantiated with the same type
    
   class BlobPtr; 
   friend bool operator==
    	(const Blob&, const Blob&); 
    // other members as in § 12.1.1 

};
class BlobPtr{};

这是因为, friend bool operator==(const Blob&, const Blob&) is declaration of function and a friend at the same time, but friend bool operator==<T>(const Blob<T>&, const Blob<T>&) is friend declaration to template instantiation. That’s different. Instantiation doesn’t declare template function.


if befriend whole whole function template, and then forward declaration isn’t needed: BlobPtr<int> and BlobPtr<long> (and generally BlobPtr<Anything>) are all friends of Blob<int>.

template <typename T> 
class Blob {
    template <typename U>
    friend class BlobPtr;     

    template <typename U>
    friend bool operator==(const Blob<U>&, const Blob<U>&); 
};

General and Specific Template Friendship

  • 一个class 可以make every instantiation of another template its friend, or limit friendship to a specific instantiation
    • foward declaration: 将一个template 的specific instantiation 声明 成friend 是必须的
    • 对于 all instantiation of template 被声明成 class 的friend, 不需要forward declaration.
    • 对于 non-template class 声明成 template(or non-template) class friend, 不需要 forward declaration
    • 注意下面例子
//forward declaration:  necessary to  befriend a specific instantiation of a template

template<typename T>class Pal;
class C{// 注意C就是个普通的class

    friend class Pal<C>; //用class C 实例化 Pal 是 C的friend, need forward declaration 

    //所有instantiation of Pal2 都是 C的friends; 不需要forward declaration 
    
    template<typename T> friend class Pal2;
};

/*
    Pal<char> -> access C2<char>
    Pal<char> -> no access C2<int>
*/

template<typename T>
class C2{ 
    
    // C2 的每个实例 将相同的实例化的Pal 声明成friend, 

    friend class Pal<T>; //a template declaration for Pal must be in scope 

    //all instances of Pal2 are friends of each instance of C2, 不需要forward declaration

    template<typename X> friend class Pal2;

    //Pal3 nontemplate class  that is a friend of every instance of C2;

    friend class Pal3; //不需要前置声明的

};

Befriending the Template’s Own Type Parameter

  • 可以让template type parameter be a friend. type 可以是 built-in type

下面例子中, 无论是什么类型instantiate Bar is friend. 比如 Foo is friend of Bar<Foo>, 即使通常上friend 是 class or function,

template <typename Type>class Bar{
    friend Type; //Grants access to type used to instantiate Bar

};

(d). Type Aliases

  • 可以用 typedef 在 template class refers to the instantiated class, 比如 typedef Blob<string> StrBlob;
  • 因为template 不是 type, cannot define a typedef refers to a template. 不能定义 a typedef typedef Blob<T> TBlob
typedef Blob<string> StrBlob;

template<typename T> using twin = pair<T, T>;
twin<string> authors; // authors is a pair<string, string>

twin<double> area; // area is a pair<double, double>


template <typename T> using partNo = pair<T, unsigned>; 
partNo<string> books; // books is a pair<string, unsigned> 

partNo<Vehicle> cars; // cars is a pair<Vehicle, unsigned>

(e). Static Members

  • Each instantiation of class has its own instance of the static members. 每一个class 实例都有自己的 static member 成员
  • 与其他static data member 相同, 只能有一个definition of each static data member of a template class. class 外定义时候需要加 template parameter listseparate ctr will be instantiated for different class instances 并初始化 0
  • 可以access static member through an object of the class type or by using scope operator to access member directly. Use a static member through the class, 必须refer to a specific instantiation.
    • 不可以直接通过 class template 名字access static member without template argument, 比如 Foo::ctr, 错误的

比如下面例子 对一个类型X, 比如Foo<X>::ctr and Foo<X>::count, 共享同样ctrcount function

template <typename T> class Foo { 
public:
    static std::size_t count() { return ctr; }
private:
    static std::size_t ctr; }; 
    // other implementation members

};

//定义static 

template<typename T> size_t Foo<T>::ctr = 0; //define and initailize ctr


// instantiates static members Foo<string>::ctr and Foo<string>::count 

Foo<string> fs;
// all three objects share the same Foo<int>::ctr and Foo<int>::count members 

Foo<int> fi, fi2, fi3;

访问static member

Foo<int> fi; // instantiates Foo<int> class

            // and the static data member ctr

auto ct = Foo<int>::count(); // instantiates Foo<int>::count

size_t = Foo<int>::ctr; //okay
ct = fi.count(); // uses Foo<int>::count

ct = Foo::count(); // error: which template instantiation?

(f). Template Parameters

  • template parameter name 没有内在含义, 通常用T, 可以用其他的名字
  • Template Parameters and Scope
    • 像normal scoping rules. Name of template parameter can be used 在被declared 之后直到end of template declaration or defintion
    • template parameters hides any declaration of that name in an outer scope
    • 如果一个名字被 template parameter 用了, name 不能被reused, 所以template parameter 不能有重名
  • Template Declarations
    • A template declaration must include the template parameters
    • 对于given template,每一个declaration and definition 必须有一样 number and kind (type or nontype) of parameters
  • Access class Types
    • 比如当我们用scope operator ::, 可以access type or static member. 但是对于template, template直到被实例化才知道是 type or static member. 但是compiler 必须知道a name 是不是 type 才能 process.
      • 比如 T::size_type *p: compiler 需要知道我们是在define a variable named p 还是将 名为size_type 的static 成员与 成员p 想乘.
    • by default, languages assume access name through scope operator is not a type. 当use type member of a template type parameter, must explicitly tell compiler that the name is a type by typename
      • 必须显式告诉 compiler the name is type, by using typename T::size_type *p. When we want to inform the compiler that a name represents a type, we must use the keyword typename, not class.
  • Default Template Arguments: default arguments for both function and class templates
    • 像 function default argument, 只有它右侧所有parameters 都是 default argument, 它才是default argument
    • 如果使用默认的template argment, 需要用空的 bracket pair <> following the template’s name

template parameters 隐藏外层作用域的名字; 如果一个名字被用于template parameter, 不能用这个名字来命名其他type

typedef double A;
template <typename A, typename B> void f(A a, B b)
{
    A tmp = a; // tmp has same type as the template parameter A, not double

    double B; // error: redeclares template parameter B 

};

// error: illegal reuse of template parameter name V 
template <typename V, typename V> // ...

declaration, 必须包括了template parameter

// declares but does not define compare and Blob

template <typename T> int compare(const T&, const T&); 
template <typename T> class Blob;

// all three uses of calc refer to the same function template

template <typename T> T calc(const T&, const T&); // declaration 

template <typename U> U calc(const U&, const U&); // declaration 

// definition of the template

template <typename Type>
Type calc(const Type& a, const Type& b) { /* . . . */ }

显式告诉compiler we want to use a type member of a template type parameter, 用typename T::value_type(). Our top function expects a container as its argument and uses typename to specify its return type and to generate a value initialized element if c has no elements.

template <typename T>
typename T::value_type top(const T& c)
{
    if (!c.empty())
        return c.back();
    else
        return typename T::value_type();
}

Default Template Arguments

下面例子定义了a second type parameter 表示 callable object, specifies that compare will use library less function-object class, instantiated with the same type parameter as compare. 和 a new function parameter f that bound to a callable object. The default function argument says that f will be a default-initialized object of type F. function template parameter 可以被推断

// compare has a default template argument, less<T> 

// and a default function argument, F()

template<typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f=F())
    if (f(v2, v1)) return 1;
    if (f(v1, v2)) return -1; 
    return 0;
}

//因为function template parameter 可以被推断

bool i = compare(0, 42); // uses less; i is -1 , T is int

// result depends on the isbns in item1 and item2

Sales_data item1(cin), item2(cin);

bool j = compare(item1, item2, compareIsbn);
//T is Sales_data, F 是 type of compareIsbn

用空的尖括号 使用 default template arguments

template <class T = int> class Numbers { // by default T is int 

public:
    Numbers(T v = 0): val(v) { }
    // various operations on numbers private:

    T val;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; // empty <> says we want the default type

(g). Member Templates

  • 一个class (普通class or template class) 可能有member function itself is template, 这种members 叫做 member templates. Member template 不能是 virtual
  • 一个template class 可以有template member, template class 和 template member 有各自的independent template parameters.
    • 当定义member template outside body of class tempalte, class template parameter list comes first, , followed by the member’s own template parameter list:
    • Instantiation: 必须supply arguments for template parameters for both class and function templates
      • 与普通function template 类似: compile deduces template arguments for the member template’s own parameter from arguments passed in the call

Member Templates of Ordianary (Nontemplate) Classes

  • a member template starts with its own template parameter list

比如定义类似 unique_ptr 的default deleter. class 有个 overloaded function-call operator that take a pointer and execute delete on given pointer. 因为想让deleter for any type, make call operator a template. member function 是 template, compiler 可以从推断call 的类型. 可以把DebugDelete 用于 deleter of a unique_ptr.

class DebugDelete { 
public: 
    DebugDelete(std::ostream &s = std::cerr): os(s) { } 
    // as with any function template, the type of T is deduced by the compiler 
    
    template <typename T> void operator()(T *p) const
        { os << "deleting unique_ptr" << std::endl; delete p; }
private:
    std::ostream &os;
};
double *p = new double;
DebugDelete d;
d(p);  // calls DebugDelete::operator()(double*), which deletes p

int *ip = new int;
// calls operator()(int*) on a temporary DebugDelete object 

DebugDelete()(ip);

TO override the deleter of a a unique_ptr. 必须supply tye type of the deleter inside brackets and supply an object of deleter type to constructor.

// destroying the the object to which p points

// instantiates DebugDelete::operator()<int>(int *)

unique_ptr<int, DebugDelete> p(new int, DebugDelete());

// destroying the the object to which sp points

// instantiates DebugDelete::operator()<string>(string*)

unique_ptr<string, DebugDelete> sp(new string, DebugDelete());

unique_ptr destructor instantiated, DebugDelete call operator will be instantiated. 上述定义会这样实例化

// sample instantiations for member templates of DebugDelete

void DebugDelete::operator()(int *p) const { delete p; } 
void DebugDelete::operator()(string *p) const { delete p; }

Member Templates of Class Templates

也可以定义class template 有自己的member template. both the class and the member have their own, independent, template parameters

比如

template <typename T> class Blob {
    template <typename It> Blob(It b, It e);
    // ... 

};

//define outside class

template <typename T> // type parameter for the class 

template <typename It> // type parameter for the constructor

Blob<T>::Blob(It b, It e): data(std::make_shared<std::vector<T>>(b, e)) {
}

instantiation:

  • 定义a1时候, 显式specify compiler 应该instantiate Blob version 是 int, constructor’s own paramerter 被deduced from type of begin(ia)end(ia)int*. Blob<int>::Blob(int*, int*);
  • a2 是instantiated Blob<int> class and instantiates constructor with It replaced by vector<short>::iterator
  • a3的定义实例化了一个 string 的 Blob,constructor template parameter It bound to list<const char*>
int ia[] = {0,1,2,3,4,5,6,7,8,9};
vector<long> vi = {0,1,2,3,4,5,6,7,8,9};
list<const char*> w = {"now", "is", "the", "time"}; 

// instantiates the Blob<int> class

// and the Blob<int> constructor that has two int* parameters 

Blob<int> a1(begin(ia), end(ia));

// instantiates the Blob<int> constructor that has

// two vector<long>::iterator parameters

Blob<int> a2(vi.begin(), vi.end());

// instantiates the Blob<string> class and the Blob<string>

// constructor that has two (list<const char*>::iterator parameters

 Blob<string> a3(w.begin(), w.end());

(h). Instantiations

  • 当template 被用的时候 才实例化, 意味着: same instantiation may appear in multiple object files
    • When two or more separately compiled source files use the same template with the same template arguments, 每一个soure file 中都含有template 的一个实例
    • 在large systems, the overhead of instantiating the same template in multiple files can become significant
    • 可以avoid this overhead through an explicit instantiation which suppress implicit instantiation. 包括了two parts: explicit instantiation declaration and explicit instantiation definition (Implicit instantiation 是:只有用到时候才instantiation )
      • compile 看见 extern template declaration, 不会generate code for that instantiation in that file
      • 可以有多个 extern declarations for a given instantiation but 只能有一个 definition for that instantiation
      • 因为compiler 自动instantiates a template when use it, 所以 extern declaration 必须出现在code 使用实例化之前
      • 当compiler 看见instantiation 定义(as opposed to a declaration), it generates code.
  • Explicit Instantiation Definitions Instantiate All Members: 与普通class template instantiations 不同, compiler instantiates all member of that class 因为不知道which member functions the program uses. 即使没有用到这个member, member也会被实例化.
    • 因此, An instantiation definition can be used only for types that can be used with every member function of a class template.

An explicit instantiation: 用下面的形式

extern template declaration; // instantiation declaration 

template declaration; // instantiation definition


//例子
// instantion declaration and definition

extern template class Blob<string>; // declaration

template int compare(const int&, const int&); // definition

From StackOverflow

explicit instantiation is useful when creating library(.lib) files that uses templates for distribution, uninstantiated template definitions are not put into object (.obj) files.

比如: std::basic_string<char,char_traits<char>,allocator<char> > (which is std::string) so every time you use functions of std::string, the same function code doesn’t need to be copied to objects. The compiler only need to refer (link) those to libstdc++.)

  • Explicit instantiation allows you to leave definitions in the .cpp file.
  • Putting definitions in .cpp files downside: external libraries can’t reuse the template with their own new classes. 只能用explicit instantiation 的 class 不能是自己定义type的 class
  • extern template prevents a completely defined template from being instantiated by compilation units, except for our explicit instantiation. This way, only our explicit instantiation will be defined in the final objects:
extern template class A<long>;

This line says that A<long> is to be explicitly instantiated according to the definitions the compiler has already seen.

例子1: 比如一个Application.cc file: 比如 sa1,sa2,i 的instantiation 会出现在任其他位置, 而a1, a2实例化 会只出现在这个file (Application.cc). Application.o 会包括了 Blob<int> along with the initializer_list 和 copy constructor. 但是compare<int> function 和 Blob<string>class 不会instantiated in Application.o. compare<int>Blob<string>的 definitions 在其他 program file 中

// Application.cc

// these template types must be instantiated elsewhere in the program

extern template class Blob<string>;
extern template int compare(const int&, const int&); 
Blob<string> sa1, sa2; // instantiation will appear elsewhere

// Blob<int> and its initializer_list constructor instantiated in this file

Blob<int> a1 = {0,1,2,3,4,5,6,7,8,9};
Blob<int> a2(a1); // copy constructor instantiated in this file

int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere

例子2: 实例化定义

///templateBuild.cc 

// instantiation file must provide a (nonextern) definition for every 

// type and function that other files declare as extern

template int compare(const int&, const int&);
template class Blob<string>; // instantiates all members of the class template

from IBM: Implicit Instantiation:只有用到时候才初始化

  • x<int>*q: declare a pointer to class, class的definition not needed and class 不会implcitly instantiated
  • s->g() 才会实例化X<float> and X<float>::g()
  • compiler 不需要下面defintion 的实例化
    • class X 当declare p
    • X<int> when pointer q declared
    • X<float> when pointer s declared
  • class 会implicitly instantiate 如果有pointer conversion or pointer to member conversion or 当delete pointer
  • 如果compiler 实例化template class 有static member, 这些static member不会被实例化. Compiler 实例化static member 只有当用static member时候. Every instantiated class template specialization has its own copy of static members
template<class T> class X {
  public:
    X* p;
    void f();
    void g();
};

X<int>* q;
X<int> r;//实例化 X<int>

X<float>* s;
r.f(); //实例化 X<int>::f()

s->g(); //实例化 X<float> and  X<float>::g()

会实例化如果有pointer conversion (derived-to-base), B<double>* r = p; 实例化 D<double>, delete q 实例化 D<int>

template<class T> class B { };
template<class T> class D : public B<T> { };
void g(D<double>* p, D<int>* q)
{
  B<double>* r = p;
  delete q;
}

Compiler 实例化static member 只有当用static member时候.

template<class T> class X {
public:
   static T v;
};
template<class T> T X<T>::v = 0;

X<char*> a;
X<float> b;
X<float> c;
a.v //实例化 static member v

If you define a template class that you only want to work for a couple of explicit types.

Put the template declaration in the header file just like a normal class.

Put the template definition in a source file just like a normal class.

Then, at the end of the source file, explicitly instantiate only the version you want to be available.

example:

// StringAdapter.h
template<typename T>
class StringAdapter
{
     public:
         StringAdapter(T* data);
         void doAdapterStuff();
     private:
         std::basic_string<T> m_data;
};
typedef StringAdapter<char>    StrAdapter;
typedef StringAdapter<wchar_t> WStrAdapter;

Source

// StringAdapter.cpp
#include "StringAdapter.h"

template<typename T>
StringAdapter<T>::StringAdapter(T* data)
    :m_data(data)
{}

template<typename T>
void StringAdapter<T>::doAdapterStuff()
{
    /* Manipulate a string */
}

// Explicitly instantiate only the classes you want to be defined.
// In this case I only want the template to work with characters but
// I want to support both char and wchar_t with the same code.
extern template class StringAdapter<char>;
template class StringAdapter<char>;
template class StringAdapter<wchar_t>;

Main

#include "StringAdapter.h"

// Note: Main can not see the definition of the template from here (just the declaration)
//       So it relies on the explicit instantiation to make sure it links.
int main()
{
  StrAdapter  x("hi There");
  x.doAdapterStuff();
}

Efficiency and Flexibility

  • smarter pointer type 展示了一个好的template design
  • Override default deleter. shared_ptr 可以 passing a callable object when we create or reset pointer. 相反 deleterunique_ptr 的 type 一部分(template parameters). User 必须提供that deleter type as an explcit template argument 当定义unique_ptr
  • 如果访问deleter 是对性能有重要影响
    • shared_ptr
      • shared_ptr 不 hold deleter as member 因为 type of deleter 不知道 until run time. 可以改变deleter type at shared_ptr lifetime. 可以假设 shared_ptr stores the pointer it manages in a member namedp. deleter 通过del 去access
      • del ? del(p) : delete p; // del(p) requires run-time jump to del's location. del(p) 运行时需要跳转到 del的地址
      • deleter stored indirectly, call del(p) 需要run-time jump to location stored in del to execute the code to which del points (需要一次运行时的跳转操作,转到del中保存的地址来执行)
    • unique_ptr
      • unique_ptr template parameter 有两个,一个是type that unique_ptr manages 里一个是 表示 deleter 的type. 因为type of deleter 是 part of type or unique_tpr. 因此deleter type 必须known at compile time
      • deleter store directly in unique_ptr object, no run-time overhead
      • del(p); // no run-time overhead del 可以是default deleter type or user-supplied type.没关系 the code that wil be executed is known at compile time. 如果Deleter 类似于 上面DebugDelele class, call 可以是 inlined at compile time
  • Binding the deleter at compile time, unique_ptr avoids the run-time cost of an indirect call to its deleter. By binding the deleter at run time, shared_ptr makes it easier for users to override the deleter.

(i). Argument Deduction

The process of determining the template arguments from the function arguments is known as template argument deduction. - compiler 根据argument find template argument 来generate a best match 的 vesion of function

  1. Conversions
  2. Function-Template Explicit Arguments
  3. Trailing Return Types and Type Transformation
  4. Function Pointers and Argument Deduction
  5. References Deduction
  6. Reference Collapsing and Rvalue Reference Parameters
  7. Understanding std::move
  8. Forwarding

1.Conversions

  • function template argument -> parameter conversion
    • top level const either in parameter or argument 都会被 ignored
    • const conversions: argument 是 a reference (or pointer) to nonconst object -> parameter 是 reference (or pointer) to const object
    • 如果parameter 是 nonreference type, A array argument 将会被 convert to a pointer to its first element. A function argument will be converted to a pointer to the function’s type. (array / function to pointer conversion 属于exact match 的)
    • Other conversions, 比如 arithmetic conversion, derived-to-base conversion, and user-defined conversion(通过constructor) 是不会被执行的
    • 注: const conversions and array or function to pointer are the only automatic conversions for arguments to parameters with template types.
  • function template 也可以用 普通参数, do not involve a template type parameter. 这些argument no special processing, 可以converted as usual to the corresponding type of the parameter. 比如可以从 derived-to-base

例子中 T 是value type,

  • call fobj, const string to value (top-level const 被忽略). call fref, s1 from string to const string& 是合法的.
  • 在数组例子中, 因为size 不一样, 类型不同, 在call fobj, 数组大小无关紧要, 因为会convert to pointer. 但是对于 fref 是非法的, 因为parameter 是 reference, array 不会convert to pointer, ab 类型不一样
template <typename T> T fobj(T, T); // arguments are copied 

template <typename T> T fref(const T&, const T&); // references

string s1("a value");
const string s2("another value");

fobj(s1, s2); // calls fobj(string, string); const is ignored 

fref(s1, s2); // calls fref(const string&, const string&)
            
            // uses premissible conversion to const on s1 

int a[10], b[42];
fobj(a, b); // calls f(int*, int*)

fref(a, b); // error: array types don't match

一个template type parameter 可以被用作more than one function parameter, 但是这些parameters必须有相同的类型, 如果deduced types 不匹配, call is error. 比如下面例子, compare takes two const T& parameters. arguments 必须有 same type. 例子中一个是 long, 一个是int, deduction fails.

long lng;
compare(lng, 1024); // error: cannot instantiate compare(long, int)

function with two type parameters: 比如下面例子, < operator must exist that can compare values of those types

template<typename A, typename B>
int flexibleCompare(const A& v1, const B& v2)
{
    if (v2 < v1) return 1;
    if (v1 < v2) return -1; 
    return 0;
}

long lng;
flexibleCompare(lng, 1024); // ok: calls flexibleCompare(long, int)

Normal Conversions Apply for Ordinary Arguments: 如果template 是普通参数,可以进行普通的conversion, 不用服从template的conversion. 比如下面例子,因为typep of parameter 不depend on template parameter convert from ofstream to ostream&

template <typename T> ostream &print(ostream &os, const T &obj)
{
    return os << obj;
}

print(cout, 42); // instantiates print(ostream&, int)

ofstream f("output");
print(f, 10); // uses print(ostream&, int); converts ofstream to ostream&

2.Function-Template Explicit Arguments

  • Function-Template Explicit Arguments:compiler有时候不能deduce the types of template argument, 比如 return type
    • Explicit template arguments are matched to Explicit template parameters from left to right. 所以一个explicit template argument 可能被忽视如果放在right-most
    • 可以用来进行 normal conversion, 如果 template type parameter is explicitly specified. 比如function argument 是 int, template parameter specify long, 会convert int to long

比如下面return type T1, impossible to deduce its type, need provide an explicit template argument, 对于T2, T3,不需要explicitly specify, compiler will deduce the type

// T1 cannot be deduced: it doesn't appear in the function parameter list

template <typename T1, typename T2, typename T3>
T1 sum(T2, T3);

Template explicit arguments 需要放在左侧. 比如下面例子call alternative_sum必须提供all three arguments 否则 error

// poor design: users must explicitly specify all three template parameters

template <typename T1, typename T2, typename T3>
T3 alternative_sum(T2, T1);

// error: can't infer initial template parameters

auto val3 = alternative_sum<long long>(i, lng);
// ok: all three parameters are explicitly specified

auto val2 = alternative_sum<long long, int, long>(i, lng);

If we explicitly specify the template parameter type, normal conversions apply.

long lng;
compare(lng, 1024); // error: template parameters don't match

//convert int to long

compare<long>(lng, 1024); // ok: instantiates compare(long, long)

//convert long to int 

compare<int>(lng, 1024); // ok: instantiates compare(int, int)

3.Trailing Return Types and Type Transformation

  • trailing return type: 有时需要user determine the return type. 需要用 decltype on function parameter
    • ordinary function return type 在 parameter 前面, 所以不行
    • trailing return type appears after parameter list, can use function’s parameter

可以用 decltype(*beg) obtain the type. 不可以用普通的表达式把return type 放前面. beg not exist until parameter list has been seen. Must use trailing return type ,因为trailing return appears after parameter list. 可以用function’s parameters

reference operator returns a lvalue, 因为type deduced by decltype is a reference. 如果fcn called on int, the return is int&.

// a trailing return lets us declare the return type after the parameter list is seen

template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
     // process the range
     
     return *beg; // return a reference to an element from the range
     
}

vector<int> vi = {1,2,3,4,5};
Blob<string> ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end()); // fcn should return int&

auto &s = fcn(ca.begin(), ca.end()); // fcn should return string&

Type Transformation

  • 比如我们要获取element by value 而不是 reference to an element. 问题是we don’t know the type. 比如pass iterator, no iterator operations that yield elements.
  • 所以获取element type, we use a library type transformation template. defined in type_traits header. type_traits ared used for template metaprogramming
  • 如果返回是type_traits::type,必须用typename, 告诉compiler that type represents a type 因为type is member of a class that depends on a template parameter.
  • 如果not possible to transform template’s parameter. type member is template parameter itself. 比如 remove_pointer<T>::type 如果T is pointer type, 则type is pointer 指向的类型, 否则no tranformation is needed, type is the same type as T
For Mod<T> where Mod is T is Mod<T>::type is
remove_reference X& or X&&
otherwise
X
T
add_const X&, const X, or function
otherwise
T
const T
add_lvalue_reference X&
X&&
otherwise
T
X&
T&
add_rvalue_reference X& or X&&
otherwise
T
T&&
remove_pointer X*
otherwise
X
T
add_pointer X& or X&&
otherwise
X*
T*
make_signed signed type
otherwise
X
T
make_unsigned unsigned X
otherwise
unsigned T
T
remove_extent X[n]
otherwise
X
T
remove_all_extents X[n1][n2]
otherwise
X
T

比如上面定义的 fcn function, 可以用remove_reference to obatin the element type. - remove_reference has a template type parameter 和一个(public) type member named type, -if instantiate with a reference type, 则type 是去reference 的type, 比如remove_reference<int&>, type member is int - 因为返回时 ::type, 必须用typename 在trailing return 前面

template<typename It>
auto fcn2(It beg, It end) ->
     typename remove_reference<decltype(*beg)>::type
//decltype(*beg) is reference, so 

//remove_reference<decltype(*beg)>::type is value 

{
     //process
     return *beg; //return a copy of an element from the range;
}

4.Function Pointers and Argument Deduction

  • when initialize or assign a function pointer from function template, compiler use the type of the pointer (等号右边的值) to deduce the template arguments(template 在等号左面)
    • When a function-template instaniate, the context allows a unique type or value to be determined for each template parameter.否则 ambiguous, not compile
    • 注意看下面例子

The type of parameters in pf1 决定 tyep of template argument for T,下面例子template argument for T is int. The pointer pf1 points to the instantiation of compare with T bound to int.

template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare(const int&, const int&)

int (*pf1)(const int&, const int&) = compare;

It is error 如果template arugment cannot be determined from function pointer type. 下面overload function pointer by different function parameter, 两个function pointer 都可以用来instantiate compare. cannot compile. 解决方法是using explicit template arguments as function parameters

// overloaded versions of func; each takes a different function pointer type

void func(int(*)(const string&, const string&));
void func(int(*)(const int&, const int&));
func(compare); // error: which instantiation of compare?

// ok: explicitly specify which version of compare to instantiate

func(compare<int>); // passing compare(const int&, const int&)

5.Template Argument Deduction and References

  • template <typename T> void f(T &p);
    • can only pass lvalue (variable or expression returns a reference type). Argument might or might not have a const type
    • normal binding rules apply.
    • const are low level, not top level, 比如 pint * const int , 则 Tint * const pointer to const int
  • template <typename T> void f2(const T&p);
    • normal binding say we can pass any kind of argument- an object, temporary, literal value. paramter 的T is top level的 or lower level的, 比如const pointer
    • 比如const char * const &p, Tconst char*, char to const object (lower-level),
    • 再比如 const int&p, Tint, const 是 lower-level 的
  • template <typename T> void f3(T &&p);`
    • pass rvalue to this parameter
    • deduced 跟lvalue reference 一样, deduced type of T is type of rvalue

lvalue reference

template <typename T> void f1(T&); // argument must be an lvalue

// calls to f1 use the referred-to type of the argument as the template parameter type

f1(i); // i is an int; template parameter T is int

f1(ci); // ci is a const int; template parameter T is const int

f1(5); // error: argument to a & parameter must be an lvalue

const lvalue reference 下面还是那三个call, 与上面不同的是 T 全是int

template <typename T> void f2(const T&); // can take an rvalue

// parameter in f2 is const &; const in the argument is irrelevant

// in each of these three calls, f2's function parameter is inferred as const int&

f2(i); // i is an int; template parameter T is int

f2(ci); // ci is a const int, but template parameter T is int

f2(5); // a const & parameter can be bound to an rvalue; T is int

rvalue reference: 接rvalue

template <typename T> void f3(T&&);
f3(42); // argument is an rvalue of type int; template parameter T is int

6. Reference Collapsing and Rvalue Reference Parameters

比如when we pass lvalue int i = 3 to a template function that has rvalue reference paramter template <typename T> void f3(T&&);. We would assume it as illegal, 但是language define two exceptions foundation for how library facilities move operate.

  • Exception One: When we pass an lvalue (e.g., i) to a function parameter that is an rvalue reference to a template type parameter (e.g, T&&), the compiler deduces the template type parameter (T) as the argument’s lvalue reference type .
  • Exception Two(normal binding rule):If we indirectly create a reference to a reference, then those references “collapse.”. Reference collapsing applies only when a reference to a reference is created indirectly, such as in a type alias or a template parameter
    • X& &, X& &&, and X&& & all collapse to type X&
    • The type X&& && collapses to X&&
  • 注意: lvalue deduced to lvalue references, rvalues are not deduced to rvalue references. Rvalues are deduced to the type itself

根据Reference Collapse rule: 我们can 上面的例子template <typename T> void f3(T&&);

 f3(i);   // argument is an lvalue; template parameter T is int&

 f3(ci); // argument is an lvalue; template parameter T is const int&

当Template parameter T deduced as reference type, collapsing rule says function parameter T&& collapses to lvalue reference type. 因此instantiation would be something like 下面形式. 因此即使 function parameter in f3 is an rvalue reference, call instantiates f3 with an lvalue reference type

// invalid code, for illustration purposes only

void f3<int&>(int& &&); // when T is int&, function parameter is int& &&

// int & && collapses to int & , like 

void f3<int&>(int&); // when T is int&, function parameter collapses to int&

two important consequences from these rules:

  • A function parameter that is an rvalue reference to a template type parameter (e.g., T&&) can be bound to an lvalue; (lvalue reference and rvalue reference to lvalue reference)
  • If the argument is an lvalue, then the deduced template argument type will be an lvalue reference type and the function parameter will be instantiated as an (ordinary) lvalue reference parameter (T&), 表示即使template function 是 rvalue reference, can be used by lvalue as well
  • 如果用rvalue pass 到 T&&, deduced type T 是 rvalue 的类型 (no reference)
  • 可以 pass any type to a function parameter that is an rvalue reference(i.e., T&&).

Writing Template Functions with Rvalue Reference Parameters The

template <typename T> void f3(T&& val)
{
     T t = val; // copy or binding a reference?
     
     t = fcn(t); // does the assignment change only t or val and t?
     
     if (val == t) { /* ... */ } // always true if T is a reference type
     
}
  • 当call f3 on rvalue, 比如literal 42, T is int. The local varaible t is initialized by copy value of val. 当we assign to t, parameter is val remains unchanged
  • 当call f3 on lvalue i, then T is int& 当define and initialize local variable t, variable has type int&. This initialization of t binds(reference) t to val. when assign to t, change val at the same time. In this instantiation of f3, if test always yields true
  • 所以 it is very hard to write code correct when types involved 是 plain (nonreference) types or reference types (尽管type transformation classes such as remove_reference can help)
    • rvalue reference 只用于两个地方, forwarding its argument or template overloading
    • function templates that use rvalue references often use overloading in the same way as before

use rvalue reference for overloading (跟以前一样), 如果下面function 不是template 的话,first version bind to modifiable rvalues and 第二个版本 bind to lvalues or const

template <typename T> void f(T&&); // binds to nonconst rvalues

template <typename T> void f(const T&); // lvalues and const rvalues

7. Understanding std::move

  • Although we cannot directly bind an rvalue reference to an lvalue, we can use move to obtain an rvalue reference bound to an lvalue.

standard defines move: 注需要用typename 来显示是 type.

  • move’s function parameter is T&& rvalue reference to template parameter type, 通过 reference collapsing, parameter can match arguments of any type. 我们可以pass lvalue or rvalue to move
  • remove_reference<T> 如果 TX&& or X&, remove_reference<T>::type is X, then remove_reference<T>::type&& is rvalue reference
  • 不能把 lvalue 绑定到 rvalue, 所以利用move, 获得的 rvalue reference 可以绑定到lvalue
// for the use of typename in the return type and the cast see § 16.1.3

 // remove_reference is covered in § 16.2.3 

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    //static_cast covered in § 4.11.3

    return static_cast<typename remove_reference<T>::type&&>(t);
}

string s1("hi!"), s2;
s2 = std::move(string("bye!")); // ok: moving from an rvalue

s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate value

How std::move Works

  • std::move(string("bye!"): call instantiatesmove<string> which is the function string&& move(string &&t)
    • The deduced type of T is string.
    • Therefore, remove_reference is instantiated with string.
    • The type member of remove_reference<string> is string.
    • The return type of move is string&&.
    • move’s function parameter, t, has type string&&.
    • body of function returns static_cast<string&&>(t). the type of t 已经是 string &&, cast does nothing
  • std::move(s1)
    • The deduced type of T is string& (reference to string, not plain string).
    • Therefore, remove_reference is instantiated with string&.
    • The type member of remove_reference<string&> is string,
    • The return type of move is still string&&.
    • move’s function parameter, t, instantiates as string& &&, which collapses to string&.
    • call instantiates move<string&> which is string&& move(string &t) 就是我们想要的, want bind rvalue reference to an lvalue.
      • the type of t is string& which cast converts to string&&

static_cast from an Lvalue to an Rvalue Reference Is Permitted: 尽管我们不能implicitly convert an lvalue to rvalue reference, 但是我们可以explictly cast an lvalue to rvalue reference using static_cast. 用cast, 这样language prevent us from casting lvalue to rvalue reference implicitly. 绑定到rvalue reference, lvalue 被 clobber(截断)

8. Forwarding

  • 一些function 需要 forward 一个或多个 arguments with their types unchanged to another function, 需要preserve everything about forwared arguments, 包括是不是const, argument 是 lvalue or rvalue
  • we an preserve all the type information by defining function parameter as rvalue reference to a template type parameter.
    • use a reference parameter ( either lvalue or rvalue) lets us preserve constness and lvalue/rvalue property of its corresponding argument, 因为 const in reference type 是 low-level
    • 还有个问题是, 所有 function parameter 都是lvalue 即使type is rvalue reference, 所以如果forward 到的function 有rvalue reference 是error, 因为不能用 lvalue 去绑定到 rvalue reference
      • 可以解决的这个问题用 forward (in utility header) that preserves the types of the original arguments
      • 不像 move, forward must be called with an explicit argument type(上面第二点). forward<T> returns a rvalue reference(T&&) to that explicit argument type.
      • 通常用 forward pass function parameter (定义为 rvalue reference) to template type parameter. Through reference collapsing on its return type, forward preserves the lvalue/rvalue nature of its given argument
      • same as move , 不用 using declaration for std::forward (cover at § 18.2.3)

即使function takes rvalue reference, parameter is consider as lvalue to an rvalue reference

template <typename T>
void pass(T&& v) {
    reference(v);
}

This is why Perfect Forwarding is needed, to get full semantics, use std::forward

template <typename T>
void pass(T&& v) {
    reference(std::forward<T>(v));
}
// do something like this
template <typename T>
void pass(T&& v) {
    reference(static_cast<T&&>(v));
}

下面例子的flip1 用来对调两个参数, 但是顶层const 和 references 都丢掉了

// template that takes a callable and two parameters

// and calls the given callable with the parameters ''flipped''

// flip1 is an incomplete implementation: top-level const and references are lost

template <typename F, typename T1, typename T2> void flip1(F f, T1 t1, T2 t2)
{
    f(f2, f1);    
}

但是上面的works fine 直到 遇到 reference parameter,比如直接call f 改变 绑定到 v2 的值, 但是 call f through flip1 不会改变原来的值, j passed to t1 是 a plain, non reference type int not int&. 相当于实例化了 void flip1(void(*fcn)(int, int&), int t1, int t2);, reference parameter 绑定到 t1 上 而不是 j

void f(int v1, int &v2) // note v2 is a reference
{ 
    cout << v1 << " " << ++v2 << endl;
}
f(42, i); // f changes its argument i

flip1(f, j, 42); // f called through flip1 leaves j unchanged

//the instantiation of the call, reference parameter 被绑定到 t1 上 而不是 j 上

void flip1(void(*fcn)(int, int&), int t1, int t2);

通过define rvalue reference 来 preserve all the type information

  • flip2 中, flip1(f, j, 42); type dedeuced for T1 is int&, collapses to int&. The reference t1 bound to j. 改变 t1 改变 j 的value
template <typename F, typename T1, typename T2> void flip2(F f, T1 &&t1, T2 &&t2)
{ 
    f(t2, t1);
}

但是还有个问题, 当call a function that has an rvalue reference parameter, 比如call g through flip2, 因为 function parameter 都是 lvalue expression. 相当于 pass lvalue to g rvalue reference parameter

void g(int &&i, int& j)
{
    cout << i <<" " << j <<endl;
}

flip2(g, i, 42); // error: can't initialize int&& from an lvalue

forward: 如果arg 是 rvalue reference to a template type parameter 会represent all the type information

  • 如果argment 是 rvalue, Type is ordinary type and forward<Type> return Type&& rvalue reference
  • 如果argument 是 lvalue, 通过 reference collapsing, Type is lvalue reference type(Type&), forward<Type> return Type& && is Type& lvalue reference type.
template <typename Type> intermediary(Type &&arg)
{
    finalFcn(std::forward<Type>(arg));
 }

所以上面flip 可以写成 下面形式, i passed as int& and 42 passed as an int&&

template<typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2){
    f(std::forward<T2>(t2, std::forward<T1>(t1)));
}

flip(g, i, 42);

(j). Overloading

  • Function templates can be overloaded by other templates or by ordinary, nontemplate functions. function 必须是 具有不同数量参数 或 不童类型 的参数
  • 函数匹配规则受 几方面影响
    • candidate functions 包括所有 function-template instantiation for which template argument deduction succeeds
    • candidate functions 是 可行的(viable),因为 template argument deduction 排除了不行的
    • 可行的 function 是根据 conversions 来rank的. 当然 conversions used to call a function template 是不多的 (const & array/function to pointer)
    • 如果有一个better than others, functions s selected. 但是如果several functions that provides equal match,那么
      • 如果仅有一个 nontemplate function equally good matches, 选择nontemplate function
      • 如果没有 nontemplate function 都是 function templates, one of these templates is more specialized(更特例化) than any of the others, the more specialized function template is called
      • 除此之外, call is ambiguous

好的定义 set of overloaded function templates 需要好的理解 relationship among types and of restricted conversons applied to arguments in template functions

Writing Overloaded Templates (注意e.g. 3)

定义 template function, returns a string, 可以用于 any type that has an output operator, to generate a string

// print any type we don't otherwise handle
	
template <typename T> string debug_rep(const T &t)
{
    ostringstream ret; // see § 8.3 

    ret << t; // uses T's output operator to print a representation of t 
    
    return ret.str(); // return a copy of the string to which ret is bound

}

define a version of debug_rep to print pointers. 注意下面function cannot print char*, 因为 IO library deines a version of << for char* values(假定pointer denotes a null-terminated character array, 打印数组内容, 而不是地址).

//打印指针的值, 后跟指针指向对象

//不能用于 char* 

template <typename T> string debug_rep(T *p)
{
    ret << "pointer: " << p; // print the pointer's own value 
    
    if (p)
        ret << " " << debug_rep(*p); // print the value to which p points
    
    else
        ret << " null pointer"; // or indicate that the p is null

    return ret.str(); // return a copy of the string to which ret is bound 

}

E.g.1: 对于 下面code, 只有第一个版本是可行的, 第二个版本不可行, 因为其需要pointer parameter. no way to instantiate a function template that expects a pointer type from a nonpointer argument, so argument deduction fails. 只有一个viable function.

string s("hi");
cout << debug_rep(s) << endl;

E.g.2: 对于下面code, both functons generate viable instantiations: - debug_rep(const string* &), which is the instantiation of the first version, T is string* - debug_rep(string*), which is the instantiation of the second version of debug_rep with T bound to string - 第二个version is better match. 因为第一个版本需要conversion of plain pointer to a pointer to const(lower-level)

cout << debug_rep(&s) << endl;

E.g.3: both tmeplates ar viable and both provide an exact match

  • debug_rep (const string * const &), the instantiation of the first version of the template with T bound to const string* (lower-level const), debug_rep 的template parameter 的 const 是 top-level const
  • debug_rep(const string*), the instantiation of the second version of the template with T bound to const string
  • debug_rep(T*) is better match 因为more specialized template. 因为debug_rep(const T&) 本质上可以用于任何类型, 包括指针类型, 而debug_rep(T*) 只能用于指针
const string *sp = &s;
cout << debug_rep(sp) << endl;

E.g.4: nontemplate function and template function

  • debug_rep<string>(const string&), template T bound to string
  • debug_rep(const string&) ordinary, nontemplate function
  • nontemplate function selected, nontemplate 和 template 比选nontemplate的, 因为it is more specialized,
string debug_rep(const string &s)
{
     return '"' + s + '"';
}
string s("hi");
cout << debug_rep(s) << endl;

E.g 5. Pointers to C-style character strings and string literals

  • debug_rep(const T&) with T bound to char[10]
  • debug_rep(T*) with T bound to const char
  • debug_rep(const string&) which requires a conversion from const char* to string, 因为需要user-defined conversion 所以不优于前两个
  • 第二个template require conversion from array to pointer(满足exact match in §6.f), 所以选择debug_rep(T*), most specialized than first one
cout << debug_rep("hi world!") << endl; // calls debug_rep(T*)

如果我们想handle character pointers as string, 可以define 两个nontemplate overloads

string debug_rep(char *p)
{
     return debug_rep(string(p));
}
string debug_rep(const char *p)
{
     return debug_rep(string(p));
}

记着declare string debug_rep(const string &s) before string debug_rep(const char *p) 否则wrong version will be called, compiler instantiate the call from template, 比如例子中如果不declare debug_rep with string, compiler instantiate template version that takes a const T&. 好习惯是declare every function in an overloaded set before any functions

template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// the following declaration must be in scope

string debug_rep(const string &);
string debug_rep(char *p)
{
   // if the declaration for the version that takes a const string& is not in scope
  
   // the return will call debug_rep(const T&) with T instantiated to string
   
   return debug_rep(string(p));
}

E.g. 6

  • “hi” 类型是 const char[3]
  • const T&a 实例化是 const char (&a)[10]
  • const T*a 实例化是 const char * const a
  • 两个都是viable,但是 const T*a is more specialized
template<typename T>
void func(const T& a) {
	cout << " in reference " << endl;
}
template<typename T>
void func(const T* a) {
	cout << " in pointer " << endl;
}

func("hi"); //print in pointer 

//因为 "hi" 是 const char array, call pointer need conversion to pointer(但属于exact match)

E.g 7 如果同时 T&&const T& 对于lvalue 都available, call T&&, 因为更specialized. 但对于 const rvalue reference, call const T& t, 因为对于T&& 需要const version, T bound to const int, 不如const T&

template<typename T>
void debug(const T& t) {
	cout << "in lvalue " << endl;
}
template<typename T>
void debug(T&& t) {
	cout << " in rvalue " << endl;
}
int r = 1;
debug(r); // print in rvalue;

const int&& i = r*3;
debug(i); //print in lvalue

(k). Variadic Templates

  1. Writing a Variadic Function Template
  2. Pack Expansion
  3. Forwarding Parameter Packs
  • variadic template is template function or class that can take varying number of parameters
    • varying parameter are known as parameter pack. There are two kinds of parameter pack - A template parameter pack represents zero or more template parameters (pack 可以是不同类型)
    • A function parameter pack represents zero or more function parameters (pack 可以是不同类型)
  • use ellipsis 表示template or function represents a pack, In template parameter list, class... or typename... 表示following parameter 是 a list of zero or more types; the name of a type followed by an ellipsis represents a list of zero or more nontype parameters of the given type.
  • As usual, compiler deduces template parameter types from function’s argument. For variadic template, compiler deduces the number of parameters in the pack
  • sizeof...(pack), how many elements in a pack(either template or function), 用sizeof...(pack), returns a constant expression and does not evaluate its argument
  • initializer_list take a varying number of arguments with the same type. Variadic functions are used when 不知道 number or types (可以是different types) of arguments we want to process
// Args is a template parameter pack; rest is a function parameter pack

// Args represents zero or more template type parameters

// rest represents zero or more function parameters

template <typename T, typename... Args>
void foo(const T &t, const Args& ... rest);

下面例子, type of T is deduced from first argument. The remainng arguments provide the number of, and types for, the addiational arguments to the function

int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // three parameters in the pack

foo(s, 42, "hi"); // two parameters in the pack

foo(d, s); // one parameter in the pack

foo("hi"); // empty pack


//compiler instantiate 4 instances of foo

void foo(const int&, const string&, const int&, const double&);
void foo(const string&, const int&, const char[3]&);
void foo(const double&, const string&);
void foo(const char[3]&);

sizeof

template<typename ... Args> 
void g(Args ... args) {
   cout << sizeof...(Args) << endl; // number of type parameters
   
   cout << sizeof...(args) << endl; // number of function parameters
   
}

1. Writing a Variadic Function Template

  • define a function print, argument types 可以变化, that will print the contents of a given list of arguments on a given stream.
  • Variadic functions 通常是 recursive 的, The call process first argument in the pack 再call itself on remaining arguments.
  • To stop the recursion, need to define a nonvariadic print function, 在我们例子中, 最好一个rest 就只有一位数, 这时call print 不会call variadic version
  • key part of printreturn print(os, rest...); // the first argument in pack rest 会被 removed from the pack and becomes the argument bound to t. The remaining arguments in rest for the next call to print 的 parameter pack
  • 注意 A declaration for nonvariadic version of print must be in scope when the variadic version is defined. Otherwise, the variadic function will recurse indefinitely.
// function to end the recursion and print the last element

// this function must be declared before the variadic version of print is defined

template<typename T>
ostream &print(ostream &os, const T &t)
{
return os << t; // no separator after the last element in the pack
}
// this version of print will be called for all 除了 last element in the pack

template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)
{
   os << t << ", "; // print the first argument

   return print(os, rest...); // recursive call; print the other arguments

}

用一个call 来说明 print(cout, i, s, 42); // two parameters in the pack , 分成了3个call, 前两个call variadic version 因为 nonvariadic version 不viable. 最后一个call print(cout, 42), variadic(parameter pack 是empty) 和 nonvariadic 都viable, 但是nonvariadic template is more specialized than variadic template, nonvariadic version is chosen

Call t rest…
print(cout, i, s, 42) i s,42
print(cout, s, 42) s 42
print(cout, 42)   call nonvariadic version

2.Pack Expansion

  • 除了获取size, we can expand the parameter pack
  • when expand a pack, 需要provide a pattern to be used on each expanded element. pattern 适用于每个被expanded 的elements
  • We trigger an expansion by putting an ellipsis (. . . ) to the right of the pattern.
  • The pattern in an expansion applies separately to each element in the pack.

比如下面例子有两个 expansions.

  • 第一个expansion: expands the template pack and generates function parameter list for print
    • The expansion of Args applies pattern const Args& to 每一个elements in template parameter pack Args.
    • The expansion of this pattern is a comma-separated list of zero or more parameter types, each of which will have the form const type&.
  • 第二个expansion happens in recusive call to print. pattern is the name of the function parameter pack (i.e., rest) (pattern 就是function parameter pack 名字自己). This pattern expands to a comma-separated list of the elements in the pack. the call 等同于 print(os, s,42)
template <typename T, typename... Args>
ostream & print(ostream &os, const T &t, const Args&... rest)// expand Args
{
     os << t << ", ";
     return print(os, rest...); // expand rest
     
}

比如有print(cout, i, s, 42);, The type of last two arguments along with pattern determine the types of trailing parameter. Call is instantiated as

ostream& print(ostream&, const int&, const string&, const int&);

Understanding Pack Expansions: more complicated pattern

下面例子用 pattern debug_rep(rest). pattern says 我们想 call debug_rep on each element in function parameter pack rest.

// call debug_rep on each argument in the call to print
template <typename... Args>
ostream &errorMsg(ostream &os, const Args&... rest)
{
	// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
	
	return print(os, debug_rep(rest)...);
}

比如call

errorMsg(cerr, fcnName, code.num(), otherData, "other", item);

等同于execute as

print(cerr, debug_rep(fcnName), debug_rep(code.num()), 
     debug_rep(otherData), debug_rep("otherData"),
     debug_rep(item));

而下面的pattern not compile.因为attempt to call debug_rep with a list of five arguments. debug_rep function is not variadic 并且也没有 debug_rep function has five parameters

// passes the pack to debug_rep;  print(os, debug_rep(a1, a2, ..., an))

print(os, debug_rep(rest...)); // error: no matching function to call


//The call is executed as 

print(cerr, debug_rep(fcnName, code.num(),
	otherData, "otherData", item));

3. Forwarding Parameter Packs

  • Under new standard, 可以use variadic templates together with forward to write functions that pass their arguments unchanged to some other function.

E.g. add emplace_back member to StrVec class.

  • Library container的 emplace_backvariadic member templates that uses arguments to construct an element directly in space managed by container
  • 必须是variadic member,因为string constructor differ in terms of parameter. 而且想要 string move constructor, 必须preserve all type information about the arguments. Perserve 分为two-step
    • define parameter as rvalue reference to template type parameter, 所以可以pass any type to function
    • must use forward to preserve arguments’ original types when emplace_back passes those arguments to construct (another function)
  • 例子中 std::forward<Args>(args)... expands both template parameter pack Args and function parameter pack args.
    • Pattern generate form std::forward<Ti>(ti): Ti: ith element in tempalte parameter pack. ti: ith element in function parameter pack
  • By using forward in this call, we guarantee if emplace_back is called with an rvalue, then construct will also get an rvalue
  • 比如 svec.emplace_back(s1 + s2);. Putting in emplace back 是 rvalue, result from forward<string> is string&&, will forward argument to string move constructor
class StrVec{
public:
   template<class ...Args> void emplace_back(Args&&... );
};

template<class ...Args> 
inline 
void StrVec::emplace_back(Args&& ... args){
     check_n_alloc();//reallocate if necessary
     
     alloc.constructor(first_free++, 
        std::forward<Args>(args) ...);
}

//假设svec is a StrVec

svec.emplace_back(10, 'c'); // adds cccccccccc as a new last element

//constructor will expand to 

std::forward<int>(10), std::forward<char>(c)


svec.emplace_back(s1 + s2); // uses the move constructor

//the argument to emplace_back is an rvalue

//The result type from forward<string> is string&&, 

//so construct will be called with an rvalue reference.

(l). Template Specializations

  1. Function Overloading versus Template Specializations
  2. Class Template Specializations
  3. Class-Template Partial Specializations
  4. Specializing Members but Not the Class
  • 使template is best for every possible tmplate argument which template 可以被实例化 是不可能的. 有时general template definition is wrong for a type. 有时候 也许write more efficient code than instantiated from template.
  • When we specialize a function template, we must supply arguments for every template parameter in the original template
    • in order to specialize a template, a declaration for original template must be in scope. declaration for a specialization must be in scope before any code uses that instantiation of the template(specilization 就是instantiation)
      • 对于普通functions, missing declaration 是容易发现的, 但是对于spcialization declaration is missing, compiler 通常generate code using original template, 因为compiler 可以用original template 实例化 when a specialization is missing. 所以declaration order error between template and its specialization 容易犯但不容易发现
      • 如果程序用了specialization and instantiation of original template with the same set of template argument 是error. 这种错误compiler unlikely to detect
      • Best Practices: templates and their specialization declared in same header. Declaration for all templatess 先appera, followed by any specializations of those template
    • to indicate specialing a template, 用 keyword template 后跟一个 empty angle brackets <>: 指出 argument will be supplied for all the template parameters of original template
    • the function parameter types(s) 必须 match corresponding types in previously declared template
    • 注意有时候function parameter type 如果是 const type, type aliases, pointer, 会annoyed. 比如function parameter 是 const T&, 要specialize const char *类, 那么特例化的function parameter 是 const char * const &(在下面例子中)

比如我们compare function, compare character pointer by strcmp 而不是 cmparing pointer values. 第二个版本 called only when pass string literal or an array. 如果对比 character pointers, first version of template will be called.

  • compare(p1, p2); calls the first template: 因为no way to convert a pointer to a reference to an array. 因此第二个版本的 compare not viable
  • compare("hi", "mom");
// first version; can compare any two types

template <typename T> int compare(const T&, const T&); 

// second version to handle string literals

template<size_t N, size_t M>
int compare(const char (&)[N], const char (&)[M]);

const char *p1 = "hi", *p2 = "mom";
compare(p1, p2); // calls the first template

compare("hi", "mom"); // calls the template with two nontype parameters

e.g. specialization 的 T 类型是 const char *, 我们template function 的parameter 是 const T&, 这里const 表示是pointer 是const, 而不是a pointer to const. 因此 need to use in specialization is const char* const &, a reference to const pointer to const char

template <typename T> int compare(const T&, const T&);

// special version of compare to handle pointers to character arrays 

template <>
int compare(const char* const &p1, const char* const &p2)
{
     return strcmp(p1, p2);
}

1. Function Overloading versus Template Specializations

  • 当定义 a function template specialization, 是take over job of compiler. supply definition to use for a specific instantiation of original template.
    • specialization is an instantiation. It is not an overloaded instance of the function name。 因此, specialization not affect function matching
  • 定义 function as specialization or independent nontemplate function 可以影响function matching
    • 注意下面例子

比如定义two versions compare, 一个是template specialization parameter 是 char (&arr)[10], 另一个是 const T&. 下面的function callcompare("hi", "mom") , 两个function template 都viable. 会选择specialization 版本的, 因为more specialized

template<typename T>
int compare(const T& a, const T&b);

template<>
compare(const char * const &a, const char * const & b );

compare("hi", "mom");

但是如果我们定义一个nontemplate function. 两个function template 和nontemplate function 都可行, 但是选择nontemplate function

compare(const char * a, const char * b);

2.Class Template Specializations

比如define a specialization of library hash template , 用来store Sales_data objects in an unordered container.

  • By default, unordered container use hash<key_type> to organize their elements. To use this default with our own data type, 必须define a specialization of hash template. 一个specializaed hash 必须定义
    • An overloaded call operator that returns a size_t and takes an object of the container’s key type as parameters
    • Two type members, result_type and argument_type, which are the return and argument types, respectively, of the call operator
    • The default constructor and a copy-assignment operator (which can be implicitly defined )
  • 必须 specialize a template in the same namespace which original template is defined. Any definition 注意namespace close no semicolon after curly brace.

注意

  • Any definitions that appear between open and close curlies 是 part of std namespace
  • Our hash<Sales_data> definition starts with template<>, which indicates that we are defining a fully specialized template. template we specialize is named hash and specialized version is hash<Sales_data>
  • As with any other class, we can define the members of a specialization inside the class or out of it, 我们定义的operator() outside class.
  • The overloaded call operator must define a hashing function over the values of the given type(Sales_data). 好的hash function 总会yield different results for unequal object
    • 这里 我们将hash function 交给了标准库, 标准库 defines specializations of the hash class for built-in types and for many of library types.
  • By default, the unordered containers use the specialization of hash that corresponds to the key_type along with the equality operator on the key type.
    • 假设specialization is in scope, it will be used automatically when we use Sales_data as a key to unordered containers
  • 因为hash<Sales_data> 使用 Sales_data private members (bookNo, revenue), 必须make hash<Sales_data> as friend of Sales_data. 因为hash<Sales_data> instantiation 定义在 std namespace, our friend declarations 使用 std::hash
  • To enable users of Sales_data to use the specialization of hash, we should define this specialization(hash) in the Sales_data header.
//open the std namespace so we can specialize std::hash

namespace std {
template <> // we're defining a specialization with 

struct hash<Sales_data> // the template parameter of Sales_data 
{   
    // the type used to hash an unordered container must define these types

    typedef size_t result_type;
    typedef Sales_data argument_type; //默认情况下, this type 需要 ==

    size_t operator()(const Sales_data& s) const;
    // our class uses synthesized copy control and default constructor

};
size_t hash<Sales_data>::operator()(const Sales_data& s) const {
    return  hash<string>()(s.bookNo) ^ 
            hash<unsigned>()(s.units_sold) ^
            hash<double>()(s.revenue);
}
} // close the std namespace; note: no semicolon after the close curly

因为使用了Sales_data private member, 必须make std::hash<Sales_data> as Sales_data friend

template <class T> class std::hash; // needed for the friend declaration

class Sales_data {
friend class std::hash<Sales_data>;
    // other members as before 

};

如果specialiation of hash in scope, will be used automatically when use Sales_data as key to unordered container,比如

// uses hash<Sales_data> and Sales_data operator==from § 14.3.1 

unordered_multiset<Sales_data> SDset;

3. Class-Template Partial Specializations

  • We can partially specialize only a class template. We cannot partially specialize a function template.
  • 不同于function templates, a class template specialization 不需要提供 argument for every template parameter. 可以specify some, but not all template parameters or some, but not all, aspects of the parameters.
  • A class template partial specialization 本身是 template. Users must supply arguments for those 特例化版本中未指定的 template parameters.

比如 remove_reference. That template works through a series of specializations

  • 第一个template 定义most general version. can be instantiated with any type. 它uses template argument as type for its member named type. 接下来两个class 是 partial specializations of original template.
  • Because a partial specialization is a template, class name 与template 一样. The specialization’s template parameter list 包括了 template parameter whose type is not fixed by this partial specialization (未被确定的参数).
  • 在class name 后的<>specify arguments for the template parameter we are specializing(提供特例化的实参). 这些arguments 与原始模板中 的参数 按位置对应.
  • The template parameter list of a partial specialization is a subset of, or a specialization of, the parameter list of the original template -在下面例子中, 特例化有一样数量的parameter as original template, parameter’s type in specialization differ from original template. specialization 用于lvalue 和 rvalue reference type
//original, most general template 

template <class T> struct remove_reference {
typedef T type;
};

// partial specializations that will be used for lvalue and rvalue references

template <class T> struct remove_reference<T&> // lvalue references

{ typedef T type; };
template <class T> struct remove_reference<T&&> // rvalue references

{ typedef T type; };

下面是三个变量 a,b,c 均为 int 类型

int i;
// decltype(42) is int, uses the original template 

remove_reference<decltype(42)>::type a;
// decltype(i) is int&, uses first (T&) partial specialization 

remove_reference<decltype(i)>::type b;
// decltype(std::move(i)) is int&&, uses second (i.e., T&&) partial specialization 

remove_reference<decltype(std::move(i))>::type c;

4. Specializing Members but Not the Class

  • 可以只特例化特定成员函数 而不是 特例化整个模板 (比如上面的hash<Sales_data>, 只特例化了call operator, result_type 和 argument_type)

e.g. 如果 Foo is a template class witha a member Bar, 可以只specialize that member. 只特例化了 Foo<int> 类的一成员, 其他类由Foo template 提供. 如果use Foo with 任何 type, members are instantiated as usual. If use Bar member of Foo<int>, 则使用我们定义的特例化版本

template <typename T> struct Foo 
{
    Foo(const T &t = T()): mem(t) { } 
    void Bar() { /* ... */ }
    T mem;
    // other members of Foo 

};

template<> // we're specializing a template

void Foo<int>::Bar() // we're specializing the Bar member of Foo<int>

{
    // do whatever specialized processing that applies to ints 

}

Foo<string> fs; // instantiates Foo<string>::Foo()

fs.Bar(); // instantiates Foo<string>::Bar()

Foo<int> fi; // instantiates Foo<int>::Foo()

fi.Bar(); // uses our specialization of Foo<int>::Bar()

From Scott Meyers

rvalue references, move semantics, or perfect forwarding. Auto variables with && 跟type deduction for function template parameter 原理是一样的. so lvalues of type T are deuced to have type T&, rvalue of type T to have type T.

declare using && 不一定表示是 rvalue reference, 比如下面code, 如果see && assume its rvalue reference, misread lot of c++ code

Widget&& var1 = someWidget;  // here, “&&” means rvalue reference, 

//but var1 is lvalue, 因为take address of var1
 
auto&& var2 = var1;// here, “&&” does not mean rvalue reference
 
template<typename T>
void f(std::vector<T>&& param);  // here, “&&” means rvalue reference
 
template<typename T>
void f(T&& param);     // here, “&&”does not mean rvalue reference

references declared with && that may be either lvalue references or rvalue references may bind to anything. Such unusually flexible references deserve their own name. I call them universal references. && indicates a universal reference only where type deduction takes place

template<typename T>// deduced parameter type ⇒ type deduction;

void f(T&& param);  // && ≡ universal reference
       
 
template<typename T>
class Widget {
    ...
    Widget(Widget&& rhs); // fully specified parameter type ⇒ no type deduction;
    
    ...       // && ≡ rvalue reference
};
 
template<typename T1>
class Gadget {
    ...
    template<typename T2>
    Gadget(T2&& rhs); // deduced parameter type ⇒ type deduction;
    
    ...    // && ≡ universal reference
};
 
void f(Widget&& param); / fully specified parameter type  no type deduction;
      
      // && ≡ rvalue reference
      

decltype is different deduction from auto, 比如下面的第二个例子, won’t compile

a) if the value category of expression is xvalue, then decltype yields T&&;
b) if the value category of expression is lvalue, then decltype yields T&;
c) if the value category of expression is prvalue, then decltype yields T.

Widget w1, w2;
 
auto&& v1 = w1; // v1 is an auto-based universal reference being
                  
	// initialized with an lvalue, so v1 becomes an
                                 
	// lvalue reference referring to w1.
 
decltype(w1)&& v2 = w2; // v2 is a decltype-based universal reference, and
             
	     // decltype(w1) is Widget, so v2 becomes an rvalue reference.
             
	     // w2 is an lvalue, and it’s not legal to initialize an
            
	    // rvalue reference with an lvalue, so
           
	   // this code does not compile.





17. Specialized Library Facilities

(a). Tuple

  1. Defining and Initializing tuples
  2. Using a tuple to Return Multiple Values
  • Tuple can have any number of members. Each distinct tuple type has a fixed number of members. 不同 tuple type 的 member 数量可以不同
  • Tuple is useful when combine some data into one object but 不想建立 a data structure.
  • tuple can be think as quick and dirty data structure

Tuple type, along with its companion types, and functions defined in tuple header.

syntax description
tuple<T1,T2, ... , Tn>t a tuple with members types are T1Tn. members are value initialized
tuple<T1, T2,..., Tn>t(v1,v2,...vn); members initialized from initializer vi. The constructor is explicit
make_tuple<v1,v2,...,vn) returns a tuple initialized from given initializer. The type of the tuple is inferred from types of initializer
t1 == t2
t1 != t2
two tuples are equal if they 一样数量member and each pair of members are equal. Use member 的 == operator. 一旦发现不一样, subsequent members are not tested
t1 relop t2 Relational operations. 两个tuples must have the 一样数量的member
get<i>(t) return reference to ith data member of t. 如果t is lvalue, result is lvalue reference, otherwise it is rvalue reference. 所有member of tuple 是 public
tuple_size<tupleType>::value A class template that can be instantitated by a tuple type and has a public constexpr static data member named valuesize_t type 表示number of members in specified tuple type, 因为是constexpr, 可以用于array declare, 比如 int a[tuple_size<..>:value]
tuple_element<i, tupleType>::type A class template that can be instantiated by an integral constant and a tuple type and has a public member named type that is type of specified members in specified tuple type

1. Defining and Initializing tuples

  • when create a tuple, use default tuple constructor, value initialized each member
  • 因为tuple constructor is explicit. must use direct initialization syntax.
  • make_tuple generateds tuple object
  • use get through library function template 必须specify explicit template argument
    • get returns a reference to specified member
    • inside bracket <> must be integral constant expression
  • To use tuple_size or tuple_element, need to know type of tuple object.如果有tuple type 不知道, 可以用 decltype.
    • tuple_size has public static data member value: 是members的数量 in specified tuple.
    • tuple_element take index 和 tuple type. has public type member type: the type of spcified member of specified tuple type. indices 从 0 开始
  • relational and equality operators:
    • 必须有 same number of members. to compare, 必须legal to use < and == to compare each pair of members (left-hand tuple vs right-hand tuple)
    • 因为tuple defines < and == operators, 可以pass sequences of tuples to algorithms and use tuples as key type in ordered container
tuple<size_t, size_t, size_t> threeD; // all three members set to 0

tuple<string, vector<double>, int, list<int>>
	someVal("constants", {3.14, 2.718}, 42, {0,1,2,3,4,5});

Tuple 必须用 direct initialization. 因为tuple constructor 是explicit tuple (const Types&... elems);explicit tuple (UTypes&&... elems);, 不能用copy constructed 的形式

tuple<size_t, size_t, size_t> threeD = {1,2,3}; // error

tuple<size_t, size_t, size_t> threeD{1,2,3}; // ok

library 定义了 make_tuple function that generates a tuple object. Uses the types of the supplied initializers to infer the type of the tuple

// tuple that represents a bookstore transaction: ISBN, count, price per book

auto item = make_tuple("0-999-78345-X", 3, 20.00);

get function 定义是, 所以必须specify explicit template argument

template <size_t I, class... Types>
typename tuple_element< I, tuple<Types...> >::type& get(tuple<Types...>& tpl) noexcept;

template <size_t I, class... Types>
typename tuple_element< I, tuple<Types...> >::type&& get(tuple<Types...>&& tpl) noexcept;

template <size_t I, class... Types>
typename tuple_element< I, tuple<Types...> >::type const& get(const tuple<Types...>& tpl) noexcept;

auto book = get<0>(item); // returns the first member of item

auto cnt = get<1>(item); // returns the second member of item

auto price = get<2>(item)/cnt; // returns the last member of item

get<2>(item) *= 0.8; // apply 20% discount

如果有tuple type 不知道, 可以用 decltype

typedef decltype(item) trans; //trans is type of item 

//return the number of members in trans

size_t sz = tuple_size<trans>::value

//cnt has the same type as second item in trans

tuple_element<1, trans>::type cnt  = get<1>(item); // is int

Relational operator

tuple<string, string> duo("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (duo == twoD); // error: can't compare a size_t and a string

tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < threeD); // error: differing number of members

tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD); // ok: b is true

2. Using a tuple to Return Multiple Values

  • common use of tuple is to return multiple values from function.

E.g. 有几个书店, 每个书店hold data for 每个transaction saved in Sales_data. structure is vector<vector<Sales_data>>, 假设每个书店的vector<Sales_data> 是按照isbn来排序的, write function to find given book, output structure 是第一个参数第几个书店发现, 第二个参数和 第三个参数表示出现的range, 因为一本书可能多个书店出现, 用vector<tuple<>>

注意:

  • 因为equal_range 需要 < operator, Sales_data 没有定义 < operator, we pass a pointer to compareIsbn function
  • 因为Sales_data 定义了 addition operator, can use accumulate, pass Sales_data as functor initialized by constructor that takes a string as starting point for summation
typedef tuple<vector<Sales_data>::size_type
	vector<Sales_data>::const_iterator
	vector<Sales_data>::const_iterator> matches;

vector<matches>
findBook(const vector<vector<Sales_data>>& files, const string &book)
{
     vector<matches> ret;
     for(auto it = files.cbegin(); it!=files.cend(); ++it)
     {
          auto found = equal_range(it.cbegin(), it.cend(), book, compareIsbn);
     	  if(found.first != found.second)
	     ret.push_push_back(make_tuple(it - files.cbegin(),
	        found.first, found.second));
     }
     return ret;
}

void reportResults(istream &in, ostream& os, 
     const vector<vector<Sales_data>>& files)
{
     string s;
     while(in >> s){
          auto trans = findBook(files, s); 
	  if(trans.empty()){
	  	cout << s << "not found in any stores"<<endl;
		continue
	  }
          for(const auto & store: trans)
	        os <<"store" << get<0>(store) << " sales: "
     		<< accumulate(get<1>(store), get<2>(store), 
		             Sales_data(s))
	        <<endl;
     }
}

(b). bitset

  • bitset defined in bitset header
  • bitset possible to deal with collections of bits 大于 longest integral type
  • Like tuple, bitset has fixed size size must be constant expression: how many bits bitset contains
  • starting 在0 的位置bits叫做 low-order bits
  • ending 在最后一位的位置bits high-order bits
  • 当use integral value as initializer for a bitset, value convert to unsigned long long and treat as bit pattern. The bits in bitset are copy of that pattern
  • 如果size of bitset 小于 number of bits, only lower-order bits from given value ar used. higher order bits beyond size discarded
  • 当initialize bitset from pointer to element in char array or string. chars in lower indices correspond to high-order bits. The indexing conventions are inversely related. The character in string with the highest subscript (the rightmost character) is used to initialize the low-order bit(the bit with subscript 0).
    • 听起来绕口, 跟读数字顺序是一样的
    • 如果string contains few characters than size, high-order bits are set to zero
syntax description
bitset<n>b b has n bit; each bit is 0. The constructor is constexpr
bitset<n>b(u) b is copy of n low-order bits of unsigned long long value u. 如果 n 大于 size of an unsigned long long, higer-order bit beyond are set to zero. The constructor is constexpr
bitset<n>b(s, pos, m, zero, one) b is copy of 从pos 开始 m 个 chars from string s. s 只能有 zero or one . 如果含有其他的 character, throws invalid_argument. The characters stored in b is 0 or . pos default 是 0,, m defaultstring::npos, zero default to ‘0’, one default to ‘1’
bitset<n>b(cp, pos, m, zero, one) 跟上面一样, copies from character array cp points. 如果m 没有提供, cp must point to C-style string. 如果m is supplied, 必须at least m characters that are zero or one starting at cp
  注: constructor takes string or char pointers are explicit

下面例子:

  • 定义 bitvec holds 32 bits
  • Like element in vector, the bits in bitset not named
  • refer positionally: start from 0 through 31.
  • bits starting at 0 are reffered to as the low-order bits
  • those ending at 31 are referred to as high-order bits.
bitset<32> bitvec(1U); // 32 bits; low-order bit is 1, remaining bits are 0

如果bitset size < given value, 只有lower-order 被copy, high-order bit 被丢弃

// bitvec1 is smaller than the initializer; high-order bits from the initializer are discarded

bitset<13> bitvec1 (0xbeef); // bits are 1111011101111

// bitvec2 is larger than the initializer; high-order bits in bitvec2 are set to zero

bitset<20> bitvec2(0xbeef); // bits are 00001011111011101111

// on machines with 64-bit long long 0ULL is 64 bits of 0, so ~0ULL is 64 ones

bitset<128> bitvec3(~0ULL); // bits 0 ... 63 are one; 63 ... 127 are zero


0xbeef = 48879
f -> 15 * (16 to power of 0) = 15 * 1 = 15
e -> 14 * (16 to power of 1) = 14 * 16 = 224
e -> 14 * (16 to power of 2) = 14 * 256 = 3584
b -> 11 * (16 to power of 3) = 11 * 4096 = 45056

initializing a bitset from a string: 可以initialize a bitset from either string or a pointer to an element in character array. 哪种情况, characeters 都表示 bit pattern directly. 但是string 的 highest suscript (最右侧的) is used to initialize low-order bit

bitset<32> bitvec4("1100"); // bits 2 and 3 are 1, all others are 0

// 也可以用substring 

string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4); // four bits starting at str[5], 1100, 剩下的都是0

bitset<32> bitvec6(str, str.size()-4); // use last four characters, 1101

operation on bitset

  • support bitwise ( § 4.8 中), have the same meaning when applied to bitset as built-in operator for unsigned operands (&, |)
  • set, reset, flip change state of bitset 是 overloaded, 如果no argument applies, the given operation to entire set
  • b.size() is constexpr and can be used when a const expression is required.
  • subscript operator is overloaded on const
    • const version 返回 bool value true if bit at given index is on, false otherwise
    • nonconst version returns a special type defined by bitset that lets us manipulate the bit value at the given index position
  • ~bitvec[0]; 等于 bitvec.flip(0)
  • b.to_ulong()(); and b.to_ullong() only work if size of bitset less than or queal to crresponding size
    • Throw an overflow_error exception (§ 5.6) if the value in the bitset does not fit in the specified type.
  • input operator reads character from input stream into temporary object of type string. Read until it has read as many characters as size of corresponding bitset, or encounter character 不是1 or 0, or it encounters end-of-file or an input error
    • then bitset is initialized from that temporary string. 如果fewer characters read than size of bitset, higher-order 用0 补充
syntax description
b.any() Is any bit in b on ?
b.all() Are all the bits in b on ?
b.none() Are no bits in b on ?
b.count() number of bits in b are on
b.size() A constexpr function that returns number of bits in b
b.test(pos) return true if bit at position pos is on, false otherwise
b.set(pos,v)
b.set()
set the bit at position pos to the bool value v. v is defaults to true, If no arguments, turns on all the bits in b
b.reset(pos)
b.reset()
Turns off the bit at position pos or turns off all the bits in b
b.flip(pos)
b.flip()
Change the state of the bit at position pos or every bit in b
b[pos] Gives access to bit in b at position pos; 如果 b is const, then b[pos] returns a bool value true if the bit is on, false otherwise
b.to_ulong()
b.to_ullong()
returns an unsigned long or unsinged long long with same bits in b. Throw overflow_error if the bit pattern in b won’t fit the indicated result type
b.to_string(zero,one) returns a string 表示 bit pattern in b. zero and one are default to '0' and '1', 表示 bits 中的 0 and 1
os << b prints the bit in b as characters 1 or 0 to stream os
is >> b Read characters from is into b. Reading stops when next character is not a 1 or 0 or when b.size() bit haven been read

例子

bitset<32> bitvec(1U); // 32 bits; low-order bit is 1, remaining bits are 0

bool is_set = bitvec.any(); // true, one bit is set

bool is_not_set = bitvec.none(); // false, one bit is set

bool all_set = bitvec.all(); // false, only one bit is set

size_t onBits = bitvec.count(); // returns 1

size_t sz = bitvec.size(); // returns 32

bitvec.flip(); // reverses the value of all the bits in bitvec

bitvec.reset(); // sets all the bits to 0

bitvec.set(); // sets all the bits to 1

bitvec.flip(0); // reverses the value of the first bit

bitvec.set(bitvec.size() - 1); // turns on the last bit

bitvec.set(0, 0); // turns off the first bit

bitvec.reset(i); // turns off the ith bit

bitvec.test(0); // returns false because the first bit is off


//subscript operator

bitvec[0] = 0; // turn off the bit at position 0

bitvec[31] = bitvec[0]; // set the last bit to the same value as the first bit

bitvec[0].flip(); // flip the value of the bit at position 0

~bitvec[0]; // equivalent operation; flips the bit at position


//Retrieving the Value of a bitset

unsigned long ulong = bitvec3.to_ulong();
cout << "ulong = " << ulong << endl;


//bitset IO Operators

bitset<16> bits;
cin >> bits; // read up to 16 1 or 0 characters from cin

cout << "bits: " << bits << endl; // print what we just read



比较code 用bitwise operator 和 bitset

bool status;
// version using bitwise operators

unsigned long quizA = 0; // this value is used as a collection of bits

quizA |= 1UL << 27; // indicate student number 27 passed

status = quizA & (1UL << 27); // check how student number 27 did

quizA &= ~(1UL << 27); // student number 27 failed


// equivalent actions using the bitset library

bitset<30> quizB; // allocate one bit per student; all bits initialized to 0

quizB.set(27); // indicate student number 27 passed

status = quizB[27]; // check how student number 27 did

quizB.reset(27); // student number 27 failed

(c). Regular Expressions

2. Error in Specifying or Using a Regular Expression
3. The Match and Regex Iterator Types
4. Using Subexpressions
5. Using regex_replace

  • RE library defined in regex header
  • 可以搜索多种类型的输入序列, Input sequence 可以是 char data or wchar_tdata and those character 被存储到 string or array or char (或则wide character versions, wstring or array of wchar_t)
    • regex hold regular expression of type char. library 还定义了 wregex class that hold type wchar_t and has same operations as regex. 唯一不同是 initializers of wregex 必须用 wchar_t 而不是 char
    • 差异还在if in string or an array. smatch represents string input sequences, cmatch character array sequence. wsmatch wide string input, wcmatch arrary of wide characters
syntax description
regex class that represent regular expression
regex_match Matches a sequence of characters against a regular expression. Returns true if entire input seqeunce matches the expression
regex_search Finds the first subseqence that mathces regular expression. Returns true if there is a substring in the input seqeunces that matches
regex_replace Replaces a regular expression using a given format
sregex_iterator Iterator adaptor that calls regex_search to iterate through the matches in a string
smatch Container class that holds the results of searching a string
ssub_match Results for a matched subexpression in a string
Arguments to regex_search and regex_match. 返回类型是bool 表示whether match was found
syntax description
(seq, m, r)
(seq, m, r, mft)
(seq, r)
(seq, r, mft)
在character sequence seq 查找regex 对象 r 中的regular expression.
seq can be a string, a pair of iterators denoting a range, or a pointer to a null-terminated character array
. m is a match object , 用来保存匹配结果细节. m and seq must have compatible types.
mft is an optional regex_constant::match_flag_type value. (in §17.13)

下面例子:

  • regex 使用的 regular expression 是 ECMAScript. 在ECMAScrip中, pattern [[:alpha:]] 表示匹配任意字母, 符号 + and * 分别表示 “one or more” or “zero or more”
    • [[:alpha:]]* will match zero or more characters
  • 我们想要 match pattern “freind” and “theif” and words “receipt” 和 “receive”
  • 定义smatch object result, 如果a match is found, results will hold details about where match occurred
  • regex_search functions stops 当只要找到一个匹配字串, results member str(), print the part of test_str that match our pattern.
    • 因此打印的是 freind
// find the characters ei that follow a character other than c

string pattern("[^c]ei");

pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*"; 
regex r(pattern); // construct a regex to find pattern

smatch results; // define an object to hold the results of a search 

// define a string that has text that does and doesn't match pattern

string test_str = "receipt freind theif receive";

// use r to find a match to pattern in test_str

if (regex_search(test_str, results, r)) // if there is a match

cout << results.str() << endl; // print the matching word

下面表中 Constructor and assignment operations 也会throw exceptions of type regex_error

regex (and wregex) Operations
syntax description
regex r(re)
regex r(re,f)
re 表示个regular expression, can be a string, a pair of iterators denoting a range of characters, a pointer to a null-terminated character array, a character pointer and a count, or a braced list of character. f is aflags that specify how object will execute. f 是通过下面列出来的值设置. If f is not specified, it defaults to ECMAScript
r1 = re r1 中正则表达式替换为 re. re(regex object) 表示一个正则表达式, can be string, a pointer to a null-terminated character array, or a braced list of character
r1.assign(re,f) Same effect as =. and optional flag f, 与regex constructor 是一样的
r.mark_count Number of subexpressions in r
r.flags() Returns the flags set for r

Flag Specified when a regex is Defined: Defined in regex and regex::syntax_option_type

syntax description
icase Ignore case during the match
nosubs Don’t store subexpression mathces
optimize Favor speed of execution over speed of construction
ECMAScript use grammar as specied by ECMA-262
basic Use POSIX basic regular-expression grammar
extended Use POSIX extended regular-expression grammar
awk Use grammar from the POSIX versionf of awk language
grep Use grammar from the POSIX versionf of grep
egrep Use grammar from the POSIX versionf of egrep

e.g. 找文件ends in .cc, or .Cc or .cC or .CC

  • . 匹配任意字符
  • we want to escape the special nature of a character by preceding with backslash. 因为blash 也是 special character, 所以用两个backslash. \\. 表示一个match 一个点
regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while (cin >> filename)
if (regex_search(filename, results, r))
    cout << results.str() << endl; // print the current match

可以搜索多种类型的输入序列。

syntax description
string regex, smatch, ssub_match, sregex_iterator
const char* regex, cmatch, csub_match, cregex_iterator
wstring wregex, wsmatch, wssub_match, wsregex_iterator
const wchar* wregex, wcmatch, wcsub_match, wcregex_iterator

第一段代码编译失败,因为 the type of match argument and type of nput sequence do not match

regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase); 
smatch results; // will match a string input sequence, but not char*

if (regex_search("myfile.cc", results, r)) // error: char* input
    
    cout << results.str() << endl;

cmatch results; // will match character array input sequences 

if (regex_search("myfile.cc", results, r))
    cout << results.str() << endl; // print the current match

2.Error in Specifying or Using a Regular Expression

  • 可以把regular expression 想成simple programming language. not interpreted by c++ compiler. Instead, a regular expression is compiled at run time when regex object is initialized with or assigned a new pattern
  • Regulation Expression 语法正确(sytactic correctness) 与否 evaluated at run time
    • if make mistake writing regular expression, run time library will throw an exceptionf of regex_error.
    • 就像standard exception types, regex_errorwhat operation describe the error that occurred
    • regex_error 也有 value code returns a numeric code 表示 type of error that was encountered
  • Avoid creating 不必要的正则表达式
    • 因为正则表达式 compiled at rune time. Compiling a regular expression 是suprising a slow operation. 除非using extended regular expression grammar or using complicated expression. 因此regex object constructor and assignment 是time-consuming
    • 如果loop 中用了regular expression, 应该在loop 外面创建 而不是recompiling it on each iteration
Regular Expression Error Conditions
syntax description
error_collate Invalid collating element request (无效元素校对请求)
error_ctype Invalid character class
error_escape Invalid escape character or trailing escape
error_backref Invalid back reference
error_brack Mismatched bracket ([or])
error_paren Mismatched parentheses ((or))
error_brace Mismatched brace ({or})
error_badbrace Invalid range inside a {}
error_range Invalid charater range e.g. [z-a]
error_space Insufficient memory to handle this regular expression (no memory to convert expression)
error_badrepeat A repetition character (*, ?, + {) 之前没有有效的正则表达式
error_complexity 要求匹配过于复杂
error_stack Insufficient memory to evaluate regular expression match specified sequence

例如我们忽略了一个方括号

try {// error: missing close bracket after alnum; the constructor will throw 
    
    regex r("[[:alnum:]+\\.(cpp|cxx|cc)$", regex::icase);
} catch (regex_error e)
{ 
    cout << e.what() << "\ncode: " << e.code() << endl; 
}

//program generates 

this program generates
regex_error(error_brack): The expression contained mismatched [ and ]. 
code: 4

3. The Match and Regex Iterator Types

  • The regex itearator are iterator adaptors(§ 9.6) that bound to an input that are bound to an input sequence and a regex object.
  • 当bind an sregex_iterator to a string and a regex object. iterator 自动定位到 first match in given string
    • sregex_iterator constructor calls regex_search on given string and regex
  • when dereference the iterator, get an smatch object corresponding to the results from most recent search.
  • when increment the iterator, it calls regex_search to find the next match in input string
syntax description
sregex_iterator it(b,e,r) it is an sregex_iterator that iterates through string denoted by iterator b and e, calls regex_search(b,e,r) to position it on the first match the input
sregex_iterator end off-the-end iterator for sregex_iterator
*it
it->
Returns a reference to smatch object or a pointer to the smatch object from the most rececent call to regex_search
++it
it++
Calls regex_search on input sequence starting just after the current match. The prefix version returns a reference to the incremnted iterator; postfix returns old value
it1 == it2
it1 != it2
如果两个regex_iterator 都是off-the-end iterator 则他们相等. Two non-end iteartors are equal if they are constructed from the same input sequence and regex object

跟上面例子一样查找所有的 [^c]ie 的pattern.

  • 当define it, sregex_iterator constructor calls regex_search to 定位 it on first match in file.
  • increment it for advances iterator by calling regex_search
  • dereference the iterator, get an smatch object represent current match.
// find the characters ei that follow a character other than c

string pattern("[^c]ei");

// we want the whole word in which our pattern appears

pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";

regex r(pattern, regex::icase); // we'll ignore case in doing the match 

// it will repeatedly call regex_search to find all matches in file

for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it){
    
    cout << "size :" << it->size() << << " position: " << it->position(0) << endl;
    
    cout <<"match :" << it->str() << " prefix: " <<it->prefix()
         << " suffix: "<<it->suffix() << endl;
}

//output 

size :1 position :9
match :rei prefix: receipt f suffix: nd theif receive
size :1 position :16
match :hei prefix: nd t suffix: f receive

Smatch Operations. These operations also apply to cmatch, wsmatch, wcmatch and the corresponding csub_match, wssub_match, and wcsub_match types

syntax description
m.ready() true if m has been set by a call to regex_search or regex_match. false otherwise. Operations on m are undefined if ready returns false
m.size() Zero if match failed; othersise, return one plus the number of subexpressions in most recently matched regular expression
m.empty() return true if m.size() is zero
m.prefix() An ssub_match representing the sequence before match
m.suffix() An ssub_match representing the sequence the part after end of the match
m.format(...) see (§ 17.12)

下面的operations that take an index, n defaults to 零 且 必须小于 m.size() . The first submatch (index 0) represents the overall match.

syntax description
m.length(n) Size of the nth matched subexpression (string 的 大小)
m.position(n) Distance of the nth subexpression from the start of the sequence
m.str(n) The matched string for the nth subexpressions
m[n] ssub_match object corresponding to the nth subexpression
m.begin(), m.end()
m.cbegin(), m.cend()
Iterators across the sub_match elements in m. cbegin and cend return const_iteartors

还是需要[^c]ie 的pattern 的例子, we call prefix returns an ssub_match object 表示 the part of file ahdead of current match. We call length on ssub_match to find, 获得前缀部分的数目字数.

for(sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it){
    auto pos = it->prefix().length(); // size of the prefix
    
    pos=pos>40?pos-40:0; //we want up to40 characters
    
    cout << it->prefix().str().substr(pos) //last part of prefix

        << "\n\t\t>>> " << it->str() << " <<<\n" // matched word 

        << it->suffix().str().substr(0, 40) // first part of the suffix
        
        << endl;
}

//打印出来形式

hey read or write according to the type 
        >>> bei <<<
ng handled. The input operators ignore whi

4. Using Subexpressions

  • A pattern in a regular expression often contains one or more subexpressions. Regular-expression grammars typically use parentheses to denote subexpressions.
  • 当用括号分组, 也就声明这些选项(allternatives) 行程a subexpression.
  • The first submatch from smatch object index at 0, 表示 match for entire pattern. 每个subexpression appears in order.
  • One common use for subexpressions is to validate data that must match a specific format.
  • ECMAScript regular expression
    • {d} 表示单个数字,{d}{n} 表示n个数字序列, 比如 {d}{3} 匹配 a sequence of three digits. In C++ \\d{3}{3}
    • A collection of characters inside square brackets allows a match to any of those character 比如 [-. ] matches a dash, a dot, or space. Note a dot has no special meaning inside bracket
    • A component followed by ? is optional. E.g. \{d}{3} [-. ]? \{d}{4} 匹配开始是三个数字, followed by an optional dash, period or space, followed by four more digits, 比如patten match 555-0132 or 555-0132 or 555 0132 or 5550132
    • Like C++, ECMAScript use backslash 表示character represent itself rather than special meaning. 因为pattern 有时include parentheses, which are special character in ECMAScipt, 比如用parentheses that are part of pattern as \(or\), 因为c++, backslash 有special meaning, 必须有双backslash. interprets \\ as an escape sequence that represents a single backslash in memory
      • regex 中必须用 双backslash \\ 才能表示单个 \ 因为string 中有单个\ 表示escape special meaning, 但是regex 接受string, construct regex时候保留string 形式, 需要string 中有 \ 形式, 所以要保留成 \ 必须接受两个backslash
      • 比如 regex reg(\\+) 因为加号有特殊含义,要escape, The actual string data get stored in memory is \+ .
      • regex reg(\\++) 表示match + 一次或者多次, 所以 string +, ++ matches.
      • regex reg("\\\\+"); Matches one or more backslashes.

Submatch Operations: These operations apply to ssub_match, csub_match, wssub_match, wcsub_match

syntax description
matched A public bool data member that 表示 whether this ssub_match was matched
first
second
public data members that are iterators to the start and one past the end of the matching sequence. 如果no match, then first and second are equal
length() The size of this match. Returns 0 if matched is false
str() Returns a string containing the matched portion of the input. Returns empty string if matched is false
s = ssub Convert the ssub_match object ssub to string s. Equivalent to s = ssub.str(). The convert operator is nonexplicit

例如下面表达式有两个parenthesized subexpression

  • ([[:alnum:]]+), which is a sequence of one or more characters
  • (cpp| cxx| cc) which is the file extension
  • 比如 the file name is foo.cpp, then results.str(0) hold foo.cpp. results.str(1) will be foo; results.str(2) will be cpp
// r has two subexpressions: the first is the part of the file name before the period 

// the second is the file extension

regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase);

print first subexpression

if (regex_search(filename, results, r))
    cout << results.str(1) << endl; // print the first subexpression

std::string s ("test subject");
std::smatch m;
std::regex e ("(sub)(.*)");

std::regex_search ( s, m, e );

for (unsigned i=0; i<m.size(); ++i) {
    std::cout << "match " << i << " (" << m[i] << ") ";
    std::cout << "at position " << m.position(i) << std::endl;
}

//print

match 0 (subject) at position 5
match 1 (sub) at position 5
match 2 (ject) at position 8

One common **use for subexpressions** is to validate data that must match a specific format. 比如美国手机号10位数, The area code enclosed in parentheses. remaining 7个 digits 可以separated by a dash, a dot or a space. or not spearated at all. 我们希望可以接受任何上面的形式 and reject 其他形式的, 首先用regular expression to find sequences that ight be phone number then call a function to complete valiation of data. 比如 (908.555.1800 we would like to reject

define regular expression using subexpressions

  1. (\\()? (\()? an optional open parenthesis for the area code
  2. (\\d{3}) the area code
  3. (\\))? an optional close parenthesis for the area code
  4. ([-. ])? an optional separator after the area code
  5. (\\d{3}) the next three digits of the number
  6. ([-. ])? another optional separator
  7. (\\d{4}) the final four digits of the number
// our overall expression has seven subexpressions: ( ddd ) separator ddd separator dddd 

// subexpressions 1, 3, 4, and 6 are optional; 2, 5, and 7 hold the number

"(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";

完整code: pattern 有7个subexpressions. 因此 smatch object contain 8个 ssub_match elements. The element at [0] represent the overall match. The elements [1]...[7] represent each of the corresponding subexpressions.

string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(phone); // a regex to find our pattern smatch m;

string s;
while (getline(cin, s)) {
    // for each matching phone number
    
    for (sregex_iterator it(s.begin(), s.end(), r), end_it;
            it != end_it; ++it)
    // check whether the number's formatting is valid 
    
    if (valid(*it))
        cout << "valid: " << it->str() << endl; 
    else
        cout << "not valid: " << it->str() << endl;
}

bool valid(const smatch& m)
{
    // if there is an open parenthesis before the area code 
    
    if(m[1].matched)
        //区号后必须有一个右括号, 之后紧跟剩余好嘛或者一个空格

        return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
    else 
        //否则 区号后不能有右括号

        //另两组部分间 分隔符必须 匹配

        return !m[3].matched && m[4].str() == m[6].str();
}

5. Using regex_replace

  • takes an input character sequence and a regex object
  • refer to a particular subexpression by using a $ followed by index number for a subexpression.
  • library 定义了 flags that we can use to control match process or formatting done during a replacement
    • These flags can be passed to regex_search or regex_match functions or format members of class smatch
Regular Expression Replace Operations
syntax description
m.format(dest,fmt,mft)
m.format(fmt,mft)
Produced formatted output using format string fmt. the match in m, and optional match_flag_type flags in mft, 第一个版本是write to output iterator dest (§10.5.1) and takes fmt either a string or a pair of pointers denoting a range in character array. 第二个版本是returns a string that holds the output and takes fmt that is a string or a pointer to null-terminated character array. mft defaults to format_default
regex_replace(dest, seq, r, fmt, mft)
regex_replace(seq, r, fmt, mft)
Iterators seq. using regex_search to find successive matches to regex r. 用format string fmt and optional match_flag_type falgs in mft to produce its output. 第一个版本是 writes to output iterator dest and takes a pair of iterators to denote seq. 第二个版本是 返回 a string 保存输出 and seq can be either string or pointer to a null-terminated character array. In all cases, fmt can be a string or pointer to null-terminated character array. mft defaults to format_default

比如上面的例子,我们想把电话转换为 “ddd.ddd.dddd“ 的形式,

string fmt = "$2.$5.$7"; // reformat numbers to ddd.ddd.dddd

string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
regex r(phone); // a regex to find our pattern

string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;

//print 908.555.1800

replace phone numbers that are embeded in large file

//比如

morgan (201) 555-2368 862-555-0123
drew (973)555.0130
lee (609) 555-0132 2015550175 800.555-0000

//转换成

morgan 201.555.2368 862.555.0123
drew 973.555.0130
lee 609.555.0132 201.555.0175 800.555.0000

string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(phone); // a regex to find our pattern

smatch m;

string s;

string fmt = "$2.$5.$7"; // reformat numbers to ddd.ddd.dddd 

while (getline(cin, s))
    cout << regex_replace(s, r, fmt) << endl; return 0;
Match Flags
syntax description
match_default Equivalent to format_default
match_not_bol Don’t treat the first character as the beginning of the line
match_not_eol Don’t treat the last character as the end of the line
match_not_bow Don’t treat the first character as the beginning of the word
match_not_eow Don’t treat the last character as the end of the word
match_any 如果存在多于一个匹配,则可返回任意一个匹配
match_not_null 不匹配任何空序列
match_continuous 匹配序列必须从输入的首字符开始
match_prev_avail 输入序列包含第一个匹配之前的内容
format_default Replacement string uses the ECMAScript rules
format_set 用 POSIX sed 规则 替换 string
format_no_copy 不输出 input序列中的未匹配部分
format_first_only Replace only the first occurence

上面例子,加上一个flag format_no_copy 不输出未匹配的部分

string fmt2 = "$2.$5.$7 "; // put space after the last number as a separator

// tell regex_replace to copy only the text that it replaces

cout << regex_replace(s, r, fmt2, format_no_copy) << endl;

//打印
201.555.2368 862.555.0123
973.555.0130
609.555.0132 201.555.0175 800.555.0000
 std::cmatch m;
 std::regex_match ( "subject", m, std::regex("(sub)(.*)") );
 std::cout << m.format ("the expression matched [$0].\n");
 std::cout << m.format ("with sub-expressions [$1] and [$2].\n");
 
//Output:

the expression matched [subject].
with sub-expressions [sub] and [ject].

(d). Random Numbers

  1. Random-Number Engines and Distribution
  2. Seeding a Generator
  3. Other Kinds of Distributions
  • Prior to new standard, Both C and C++ 依靠于 simple C library function rand. 函生成均匀分布(uniformly distributed)的 伪随机数(pseudorandom integers). 范围是从 0 到 system-dependent maximum value at least 32767. 但rand 有一些问题:
    • 一些程序需要different range from produced by rand, 一些需要random floating-point numbers. Some 需要non-uniform distribution. Programmers often introduce nonrandomness when they try to transform the range, type, or distribution of the numbers generated by rand
  • defined in random header, 解决了这些问题through a set of cooperating classes: random-number engines and random-numberdistribution classes.
    • An engine generates a sequence of unsigned random numbers.
    • A distribution uses an engine to generate random numbers of a specified type, in a given range, distributed according to a particular probability distribution.
  • C++ programs 不应该使用 rand function, 而是使用 default_random_engine along with an appropriate distribution
  • output of calling a default_random_engine similar to output of rand. Engines 生成unsinged intergers in system-defined range. rand 生成范围是 0 到 RAND_MAX
  • The range of an engine type is returned calling e.min() and e.max() members on an object of that type
Random Number Library Compoinents
Name description
Engine Types that generate a sequence of random unsigned integers
Distribution Types that use an engine to return numbers according to a particular probability distribution

engine 的 min and max

cout << "min: " << e.min() << " max: " << e.max() << endl;
//print min: 1 max: 2147483646

1. Random-Number Engines and Distribution

  • random-number engines are function-object classes 定义了call operator takes no arguments and returns a random unsigned number
    • 如果 e is engine, e() is to obtain the next random number.
  • The library defines several random-number engines that differ in terms of their performance and quality of randomness. 每一个compiler 都会指定其中一个做为 default_random_engine type。 此类型一般具有最常用的特性
    • 大多数情况, the output of an engine is not directly used. The problem is that the numbers usually span a range that differs from the one we need. Correctly transforming the range of a random number is surprisingly hard.
  • To get a number in a specified range, we use an object of a distribution type:
    • Like engine type, distribution types are also function-object classes. Distribution types 定义了 call operator that takes a random-number engine as its argument. Distribution object 用engine to produce random number maps to specified distribution
    • 如果pass the engine object itself, u(e) (u is distribution, e is engine). 如果我们写成 u(e()) 含义变成, 将e 生成下一个值传递给 u, 会compile-time error. We pass the engine, not the next result of the engine, 因为some distribution may need to call the engine more than once
Random Number Engine Operations
Syntax Description
Engine e; Default constructor; uses the default seed for the engine type
Engine e(s); Uses the integral value s as the seed
e.seed(s) Reset the state of engine using the seed s
e.min()
e.max()
此引擎可生成的最小值和最大值
Engine::result_type 此引擎生成的 unsigned 整数类型 (integral type)
e.discard(u) Advance the engine by u steps; us has type unsigned long long

E.g.

default_random_engine e; // generates random unsigned integers 

for (size_t i = 0; i < 10; ++i)
    // e() "calls" the object to produce the next random number 
    
    cout << e() << " ";//生成random numer 16807 282475249...

use an object of a distribution type to geet a number in specified range. 注意只能写成 u(e), 不能是 u(e())

// uniformly distributed from 0 to 9 inclusive 

uniform_int_distribution<unsigned> u(0,9); 
default_random_engine e; // generates unsigned random integers 

for (size_t i = 0; i < 10; ++i)
    // u uses e as a source of numbers
    
    // each call returns a uniformly distributed value in the specified range 
    
    cout << u(e) << " ";

2.Seeding a Generator

  • 即使numbers that are generated appear to be random, a given generator returns the same sequence of number 每次run
    • It’s very helpful during testing
  • A given random-number generator always produces the same sequence of numbers. A function with a local random-number generator should make that generator (both the engine and distribution objects) static. Otherwise, the function will generate the identical sequence on each call.
  • 产生相同的随机数在Debugging 时候有用, 一旦完成debugging, want each run to generate different random results, by providing a seed. 种子是一个数值, 引擎可以利用它从sequence中重新开始生成新的随机数. 两种方式seed an engine
    • provide seed when create an engine
    • call the engine’s seed member
  • Pick a good seed 就像generate a good random numer,是非常难的. 最常见的方法call system timefunction (defined in ctime header), return number of seconds since a given epoch. time function takes a single paramter that is a pointer 指向用于写入时间的数据结构</spam>. 如果pointer 是空, the function just returns the time
    • 因为```time`` 返回time 以秒计算, 因此这种方式值用于生成种子 间隔为秒级 或更长的应用
      • Using time as a seed usually doesn’t work if the program is run repeatedly as part of an automated process; it might wind up with the same seed several times.

比如, print equval, 因为v1v2 具有一样的值, 所以编写此函数的好方法 make the engine and associated distribution objects static

vector<unsigned> bad_randVec()
{
    default_random_engine e; 
    uniform_int_distribution<unsigned> u(0,9); 
    vector<unsigned> ret;
    for (size_t i = 0; i < 100; ++i)
        ret.push_back(u(e)); 
    return ret;
}

vector<unsigned> v1(bad_randVec());
vector<unsigned> v2(bad_randVec());
// will print equal

cout << ((v1 == v2) ? "equal" : "not equal") << endl;

//static 

vector<unsigned> good_randVec()
{
    // because engines and distributions retain state, they usually should be 
    
    // defined as static so that new numbers are generated on each call
    
    static default_random_engine e;
    static uniform_int_distribution<unsigned> u(0,9); 
    vector<unsigned> ret;
    for (size_t i = 0; i < 100; ++i)
        ret.push_back(u(e)); 
    return ret;
}

Provide a seed : e1e2 有不同的 seeds 生成different sequences. e3e4 有一样的seed value. 生成一样的 sequence.

default_random_engine e1; // uses the default seed 

default_random_engine e2(2147483646); // use the given seed value 

// e3 and e4 will generate the same sequence because they use the same seed 

default_random_engine e3; // uses the default seed value 

e3.seed(32767); // call seed to set a new seed value 

default_random_engine e4(32767); // set the seed value to 32767

for (size_t i = 0; i != 100; ++i) {
    if (e1() == e2())
        cout << "unseeded match at iteration: " << i << endl;   
    if (e3() != e4())
        cout << "seeded differs at iteration: " << i << endl; 
}
default_random_engine e1(time(0)); // a somewhat random seed

3. Other Kinds of Distributions

  • engines 产生 unsigned numbers 每个number in engine’s range 产生概率是相同的. Applications 通常使用 different types or distributions.
    • The library handles both 上面的needs 通过defining different distribution, when used an engine, produce the desired results
Distribution Operations
Syntax Description
Dist d; Default constructor; makes d ready to use. Other constructors depend on type of Dist (在附录 A.3.). The distribution constructor are explicit
d(e) Successive calls with the same e produce a sequence of random numbers acoording to distribution type of d. e is a random-number engine generator
d.min()
d.max()
Return the smallest and largest numbers d(e) will generate
d.reset() Reestablish the state of d so that subsequent uses of d don’t depend on value d has already generate

Generating Random Real Numbers

  • 最常见但是 Incorrect way to obtain a random floating-point from rand is rand()/RAND_MAX (RAND_MAX: system-defined upper limit that is the largest random number that rand can return)
    • 错误原因是 less precision: random integer 的精度 通常 低于 random floating-point number, 因此some floating-point values that will never be produced as output
  • With new library, 可以获取floating-point random number, define an object type uniform_real_distribution: let library mapping random integers to random floating-point number
    • 就像uniform_int_distribution, uniform_real_distribution: 需要specifiy minimum and maximum value when define
  • The distribution types are templates that have a single template type parameter that represents type of the numbers that the distribution generates. These types always generate either a floating-point type or an integral type.
    • 每一个 distribution template 都有一个default template argument. Distribution 用于 generate floating-point values 默认是 double. Distribution 用于生成 integral type 默认值是 int. 当用default: 在template’s name后提供一个 empty angle bracket <> 表示使用 default

E.g.

default_random_engine e; // generates unsigned random integers 

// uniformly distributed from 0 to 1 inclusive

uniform_real_distribution<double> u(0,1); 
for (size_t i = 0; i < 10; ++i)
    cout << u(e) << " ";

use distribution template default template type parameter

// empty <> signify we want to use the default result type 

uniform_real_distribution<> u(0,1); // generates double by default

Generating Numbers That Are Not Uniformly Distributed

  • The library 定义了 20 种 distribution types, 在附录(§ A.3, p6781 or PDF p882)

E.g. generate a series of normally distributed values. 因为normal_distribution generates floating-point number, 程序使用 cmath header 中的 lround function to round 每个 result to nearest integer. 生成200个数, mean = 4 and std = 1.5

default_random_engine e; // generates random integers 

normal_distribution<> n(4,1.5); // mean 4, standard deviation 1.5

vector<unsigned> vals(9); // nine elements each 0 

for (size_t i = 0; i != 200; ++i) {
    unsigned v = lround(n(e)); // round to the nearest integer 

    if (v < vals.size()) // if this result is in range

        ++vals[v]; // count how often each number appears 

}
for (size_t j = 0; j != vals.size(); ++j)
    cout << j << ": " << string(vals[j], '*') << endl;

生成结果为, 注: 如果打印出的图是完美对称的(perfectly symmetrical). 反倒有理由质疑random number generator了

0: ***
1: ********
2: ********************
3: **************************************
4: ********************************************************** 
5: ******************************************
6: ***********************
7: *******
8: *

The bernoulli_distribution Class

  • one distribution that does not take a template parameterbernulli_distribution, which is an ordinary class, not template
  • The distribution always return a bool value. returns true with a given probability. By default that probability is 0.5
  • declare engines outside of loops. 否则create a new engine on each iteration and 产生the same value on each iterator. 同样的 distribution may retain state and should be defined outside loop

E.g. 一个游戏,用户或者程序必须先行,可以用uniform_int_distribution 来选择, 也可以用bernoulli_distribution 来选择, 假定有个 play function 进行游戏.

string resp;
default_random_engine e; // e has state(保持状态), so it must be outside the loop!

bernoulli_distribution b; // 50/50 odds by default

do {
    bool first = b(e); // if true, the program will go first 

    cout << (first ? "We go first"
            : "You get to go first") << endl; 
    // play the game passing the indicator of who goes first

    cout << ((play(first)) ? "sorry, you lost"
        : "congrats, you won") << endl;
    cout << "play again? Enter 'yes' or 'no'" << endl; 
} while (cin >> resp && resp[0] == 'y');

如果有beeter chancing of going first

bernoulli_distribution b(.55); // give the house a slight edge

(e). The IO Library Revisited

  1. Formatted Input and Output
  2. Unformatted Input/Output Operations
  3. Random Access to a Stream

1. Formatted Input and Output

  • The library 定义 a set of manipulators that modify the format state of a stream.
  • A manipulator is a function or object that affects the state of a stream and can be used as an operand to an input or output operator. Like the input and output operators, a manipulator returns the stream object to which it is applied, so we can combine manipulators and data in a single statement.
    • 大多数manipulator 改变format state 所以提供了 set/unset pairs.
    • Manipulators that change the format state of the stream usually leave the format state changed for all subsequent IO
      • useful when want to use the same formatting.
      • 很多程序(程序员) 期望stream state as default. 所以最好undo state change 当不再需要format
  • endl: It writes a newline and flushes the buffer.
  • Manipulators are used for two broad categories of output control
    • controlling the presentation of numeric values
    • controlling the amount and placement of padding
  • The setprecision manipulators and other manipulators that take arguments are defined in the iomanip header.
  • 默认情况, floating-point values print 精度是小数点后六位数; 如果没有fractional part, 不打印小数点.
  • 根据number value 决定print either fixed decimal or scientific notation. Library chooses a format that enhances readbility of number. 非常大或者非常小的数 print using scientific notation. 其他print in fixed decimal
Manipulators Defined in iostream
Syntax (* 表示default stream state) Description
boolalpha 将 true 和 false 输出为strings
* noboolalpha true 和 false 输出为 0,1
showbase Generate prefix 表示numeric base of integral values
* noshowbase Do not generate notaional base prefix
showpoint Always dislay a decimal point for floating-point values
* noshowpoint Display a decimal point only if the value has a fractional part
showpos Display + in nonnegative numbers
* noshowpos Do not display + in nonnegative numbers
uppercase Generate upper-case letters for string
* nouppercase Don’t Generate upper-case letters for string
* dec 整型值显示为十进制
hex 整型值显示为十六进制(hexadecimal)
oct 整型值显示为八进制(octal)
left Add fill characters to the right of the value (左对齐)
right Add fill characters to the left of the value (右对齐)
internal Add fill characters between the sign and the value
fixed Display floating-point values in decimal notation
scientific Display floating-point values in scientific notation
hexfloat Display floating-point values in hex(new to C++11)
defaultfloat Reset the floating-point format to decimal (new to C++11)
unitbuf Flush buffers after every output operation
* nounitbuf Restore the normal buffer flushing
* skipws Skip whitespace with input operators
noskipws Do not skip whitespace with input operators
flush Flush the ostream buffer
ends Insert null, then flush the ostream buffer
endl Insert newline, then flush the ostream buffer
Manipulators Defined in iomanip
Syntax Description
setfill(ch) Fill whitespace with ch
setprecision(n) Set floating-point precision to n
set(w) Read or write value to w characters
setbase(b) Output integers in base b

(A). Controlling the Format of Boolean Values:

By default, bool values print as 1 or 0. 可以override this formatting by applying boolalpha manipulator

cout << "default bool values: " << true << " " << false
     << "\nalpha bool values: " << boolalpha
    << true << " " << false << endl;
//print

default bool values: 1 0 
alpha bool values: true false

To undo the format state change to cout, we apply noboolalpha. 下面code change the format of bool values only to print the value of bool_val. 一旦完成, reset the stream back to its initial state

bool bool_val = get_status();
cout << boolalpha // sets the internal state of cout

    << bool_val
    << noboolalpha; // resets the internal state to default formatting

(B). Specifying the Base for Integral Values:

Notice that like boolalpha, 下面这些 manipulators change the format state. They affect the immediately following output and all subsequent integral output until the format is reset by invoking another manipulator.

cout << "default: " << 20 << " " << 1024 << endl;
cout << "octal: " << oct << 20 << " " << 1024 << endl; 
cout << "hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: " << dec << 20 << " " << 1024 << endl;
//print 

default: 20 1024 
octal: 24 2000 
hex: 14 400 
decimal: 20 1024

(C). Indicating Base on the Output

默认没有cue as to what notational base was used. 比如 20, really 20, or an octal representation of 16? 如果我们需要print octal or hexadecimal values, we should use showbase manipulator.

  • A leading 0x indicates hexadecimal(十六进制).
  • A leading 0 indicates octal(八进制).
  • The absence of either indicates decimal(十进制).
cout << showbase; // show the base when printing integral values 

cout << "default: " << 20 << " " << 1024 << endl;
cout<<"inoctal:"<<oct <<20<<""<<1024<<endl; 
cout<<"inhex:"<<hex <<20<<""<<1024<<endl; 
cout << "in decimal: " << dec << 20 << " " << 1024 << endl; 
cout << noshowbase; // reset the state of the stream

//print 

default: 20 1024
in octal: 024 02000 
in hex: 0x14 0x400
in decimal: 20 1024

默认情况下, hexadecimal 前导符号 x是小写. We can display the X and the hex digits a–f as uppercase by applying the uppercase manipulator: uppercase. Then apply the nouppercase, noshowbase, and dec manipulators to return the stream to its original state.

cout << uppercase << showbase << hex
<< "printed in hexadecimal: " << 20 << " " << 1024 
    << nouppercase << noshowbase << dec << endl;
//print 

printed in hexadecimal: 0X14 0X400

(D). Controlling the Format of Floating-Point Values

可以控制:

  • How many digits of precision are printed
  • Whether the number is printed in hexadecimal, fixed decimal, or scientific notation
  • Whether a decimal point is printed for floating-point values that are whole numbers

(E).Specifying How Much Precision to Print

The precision member is overloaded

  • 一个版本是 takes an int value and sets the precision to that new value. It returns previous precision value
  • 另一个版本是 take no arguments and returns the current precision value.
  • setprecision takes an argument which it uses to set the precision

下面sqrt functin 定义在 cmath header 中. 不同的 sqrt function overloaded and can be called on either a float, double or long double argument. 返回square root of its argument

// cout.precision reports the current precision value

cout << "Precision: " << cout.precision()
    << ", Value: " << sqrt(2.0) << endl; 

// cout.precision(12) asks that 12 digits of precision be printed

cout.precision(12);
cout << "Precision: " << cout.precision()
    << ", Value: " << sqrt(2.0) << endl;

// alternative way to set precision using the setprecision manipulator

cout << setprecision(3);
cout << "Precision: " << cout.precision()
    << ", Value: " << sqrt(2.0) << endl;

//print 
Precision: 6, Value: 1.41421 
Precision: 12, Value: 1.41421356237 
Precision: 3, Value: 1.41

(F). Specifying the Notation of Floating-Point Numbers

  • 除非需要control presentation of floating-point number (比如 print data in columns or print data 表示money or percentage). 最好用library choose the notation

  • scientific: scientific notation
  • fixed: change the stream to use fixed decimal
  • hexfloat: force floating-point values to use
  • defaultfloat: returns the stream to its default state: 根据打印的值选择计数法
  • 上面这些manipulator 也改变了 meaning of precision.
    • After executing scientific, fixed, or hexfloat, the precision value controls the number of digits after the decimal point(控制小数点后面的数字位数).
    • By default, precision specifies the total number of digits—both before and after the decimal point(默认是小数点前和小数点后 数字总位数).

注意 下面例子默认情况下, e used in scientific notation are printed in lowercase, 可以用 uppercase manipulator 变成 upppercase

cout << "default format: " << 100 * sqrt(2.0) << '\n'
     << "scientific: " << scientific << 100 * sqrt(2.0) << '\n'
     << "fixed decimal: " << fixed << 100 * sqrt(2.0) << '\n' 
     << "hexadecimal: " << hexfloat << 100 * sqrt(2.0) << '\n'
     << "use defaults: " << defaultfloat << 100 * sqrt(2.0) << "\n\n";

//print 

default format: 141.421 
scientific: 1.414214e+002 
fixed decimal: 141.421356 
hexadecimal: 0x1.1ad7bcp+7 
use defaults: 141.421

(G). Printing the Decimal Point

默认情况下, 不打印小数点. The showpoint manipulator forces the decimal point to be printed. The noshowpoint manipulator reinstates the default behavior. The next output expression will have the default behavior 既浮点值的小数部分为0时不打印小数点

cout << 10.0 << endl; // prints 10 

cout << showpoint << 10.0 // prints 10.0000

    << noshowpoint << endl; // revert to default format for the decimal point

(H). Padding the Output

  • setw specify minimum space for the next numeric or string value
  • left to left-justify the output
  • right : right-justify the output. Output is right-justified by default
  • internal: 控制负数符号位置,它左对齐符号,右对齐值, 用空格填满(padding)中间所有空间
  • setfill: 允许指定字符代替默认空格来补白输出(pad the outut)
  • 注意setwendl 类似,不改变stream internal state, 只决定下一个输出大小
int i = -16;
double d = 3.14159;
// pad the first column(补白第一列) to use a minimum of 12 positions in the output

cout << "i: " << setw(12) << i << "next col" << '\n'
    << "d: " << setw(12) << d << "next col" << '\n'; 

// pad the first column and left-justify all columns

cout << left
    << "i: " << setw(12) << i << "next col" << '\n' 
    << "d: " << setw(12) << d << "next col" << '\n'
     << right; // restore normal justification


// pad the first column and right-justify all columns 

cout << right
    << "i: " << setw(12) << i << "next col" << '\n'
    << "d: " << setw(12) << d << "next col" << '\n'; 

// pad the first column but put the padding internal to the field

cout << internal
    << "i: " << setw(12) << i << "next col" << '\n'
    << "d: " << setw(12) << d << "next col" << '\n'; 

// pad the first column, using # as the pad character

cout << setfill('#')
    << "i: " << setw(12) << i << "next col" << '\n' 
    << "d: " << setw(12) << d << "next col" << '\n' 
    << setfill(' '); // restore the normal pad character

//print

i:         -16next col
d:     3.14159next col
i:-16         next col     
d:3.14159     next col
i:         -16next col
d:     3.14159next col
i:-         16next col
d:     3.14159next col
i:-#########16next col 
d:#####3.14159next col

(I). Controlling Input Formatting

  • 默认情况下, input operators 忽略whitespace (blank, tab, newline, formfeed, and carriage return)
  • The noskipws manipulator causes the input operator to read, rather than skip, whitespace.

默认情况下 下面code,

char ch;
while (cin >> ch)
    cout << ch;

Input:

a b  c 
d

执行 four times to read the characters a through d. The output from program is skipping the intervening blanks, possible tabs, and newline characters Output:

abcd

The noskipws manipulator causes the input operator to read, rather than skip, whitespace.

cin >> noskipws; // set cin so that it reads whitespace 

while (cin >> ch)
    cout << ch;
cin >> skipws; // reset cin to the default state so that it discards whitespace

this loop makes seven iterations, reading whitespace as well as the characters in the input. Output:

a b  c 
d

2.Unformatted Input/Output Operations

  • library provides low-level operations 支持 unformatted IO. 让我们deal with stream as a sequence of uninterpreted bytes
Single-Byte Low-Level IO Operations
Syntax Description
is.get(ch) Put the next byte from istream is in character ch(读取放进ch), Returns is
os.put(ch) Put the character ch onto ostream os. Returns os
is.get() Returns next byte from is as an int
is.putback(ch) Put the character ch back on is; return is
is.unget() Move is back one type(向后移动一个字节, 将读取的值退回流); return is
is.peek() Return the next byte as int but doesn’t remove it

Single-Byte Operations: read 而不是 ignore whitespace. 比如下面例子preserves the whitespace in input. Output 跟 input 一样. 与之前用noskipws 一样

char ch;
while (cin.get(ch))
    cout.put(ch);

Putting Back onto an Input Stream

  • peek: returns a copy of next character on input stream but not change the stream. peek 返回的值仍留在 stream 中
  • unget: 使流向后移动, 从而最后读取的值又回到流中, 即使不知道最后从六中读取什么值, 仍然可以调用unget
  • putback: 特殊版本的unget,退回从流中读取的最后一个值, 接受一个参数, 此参数必须与最后读取的值相同
  • 注: 只能put back at most one value before next read. 不保证call putback or unget successively without an intervening read operation.

int Return Values from Input Operations

  • getpeek 都返回int,而不是char. 因为int allow them to return an end-of- file marker. char 没有表示end-of-file 的
  • 返回int 的函数 将他们要char convert 到 unsigned char 再promote to int. 因此character set 有character map 到负数, the int returned from 将会是正的.
  • library 用negative value 表示 end-of-file. 保证了与任何合法char的值都不同.
    • cstdio 定义了一个名为 EOF 的 const, 可以用它检测从 get 返回的值是否为 end-of-file
    • 非常重要要用 int hold the return from the function
  • 建议用 type-safe, higher-level IO operations 而不是 low-level IO operations
int ch; // use an int, not a char to hold the return from get() 

// loop to read and write all the data in the input

while ((ch = cin.get()) != EOF)
    cout.put(ch);

如果用了 char 而不是 int, 如果在一台 char implemented as unsigned char 的机器, 将会是灾难,程序永远不会停止, 因为get 返回 EOF 时, 被转换为 unsigned char, 转化得到的值不在于 int 值相等, 在一台 char implemented as signed char 的机器, 不能确定loop的行为. what hapens to an out-of-bounds value 被assigned to a signed value 取决于compiler. 很多机器上可以正常工作除非input matches EOF valus(尽管普通input 不太可能). 比如 输入包含\377 , 循环终止, 因为我们机器上, -1 转换为sign char, 就会得到 \377

char ch; // using a char here invites disaster!

// the return from cin.get is converted to char and then compared to an int

while ((ch = cin.get()) != EOF)
    cout.put(ch);

Multi-Byte Operations

  • 下面表中operatios require us to allocate and manage character arrays 用于store and retrieve data.

Multi-Byte Low-Level IO Operations:

  • is.get(sink,size, delim) :
    • is 中 最多读取 size 个 bytes and stores them in char array beginning at the address pointed by sink. 读取过程直至遇到字符 delim 或读取 size 个字节 或者遇到文件尾时停止, 如果遇到了delim 则将其保留在input stream and not read delim into sink
  • is.getline(sink,size,delim):
    • 与上面版本类似,但会读取并丢弃 delim
  • is.read(sink,size):
    • Reads up to size bytes into character array sink. Returns is
  • is.gcount():
    • Returns number of bytes read from the stream is by last call to an unformatted read operation
    • 可以call gcount before any intervening unformatted input operation. 特别的是, single-character operation hat put character back to tream 属于 unformatted input operations. 如果 peek, unget, or putback are calling before gcount, return value will be 0 </span>
  • os.write(source, size)
    • Writes size bytes from the character array source to os. Returns os
  • is.ignore(size, delim)
    • 读取并忽略最多 size 个字符, 包括delim. 不像其他未格式化的函数, ignore has default arguments: size defaults to 1 and delim to end-of-file.
  • getgetline functions 接受相同的参数, actions are similar but not identical.
    • 相同的是 sink 都是一个char array 用来保存数据. delimiter 都不会存到 sink
    • 不同是 treat delimiter differently.get leaves delimiter as next character in istream. getline read 并丢弃 delimiter.
    • 两个function 都一直读取数据直到:
      • 已经读取 size-1 个characters
      • End-of-file is encountered
      • The delimiter character is encountered

3.Random Access to a Stream

  • library provides funcion seek to given localtion and tell current location in associated stream
  • 尽管seek and tell are defined for all stream types, 但是比如绑定到cin, cout, cerr, clog 流 不支持random accesss 是没有意义的, , 比如count 直接输出, 向回跳10个位置没有意义
    • 对这些流 seek and tell, will fail at run time, leave the stream in an invalid state
  • Support random access, IO types matiner a marker that determines where the next read or write will happen</span>
    • 定义了两对 seek and tell and distinguiushede by suffix: g 表示 getting, p 表示 putting
      • 只能用 g version on an istream and on ifstream and istringstream (inhert from istream), 不能用 seekg.
      • 只能用 p version on ostream 和 它的子类 ofstream and ostringstream.
    • iostream, fstream, stringstream 可以用both types, either g or p version
  • There Is Only One Marker: 即使有seek and tell 函数, 但不存在 read marker and writ marker.
    • Because there is only a single marker, we must do a seek to reposition the marker whenever we switch between reading and writing.
    • 比如fstreamstringstream 可以read and write the same stream. 但只有一个buffer that holds data to be read and written and a single marker 表示current position in buffer. library maps both g and p positions to this buffer
Seek and Tell Functions
Syntax Description
tellg()
tellp()
Return current position of the marker in input stream (tellg) or an output stream (tellp)
seekg(pos)
seekp(pos)
Reposition the marker in input or output stream to given absolute address in stream. pos 通常是前一个 tellg or tellp 的返回值
seekp(off,from)
seekg(off,from)
字啊一个输入流或者输出流中将标记定位到from 之前或之后 off 个字符, from 可以是下列值之一
  - beg seek relative to the beginning of the stream
  - cur seek relative to the current position of the stream
  - end seek relative to the end of the stream

Repositioning the Marker

  • 参数 new_positionoffset类型分别为 pos_typeoff_type. 都是machine-dependent types(各自defined in istreamostream).
  • pos_type表示 file position and off_type 表示offset from that position. value of type off_type 可以是正的或者负的, can sek forward or backward in file
// set the marker to a fixed position

seekg(new_position); // set the read marker to the given pos_type location 

seekp(new_position); // set the write marker to the given pos_type location

// offset some distance ahead of or behind the given starting point

seekg(offset, from); // set the read marker offset distance from from 

seekp(offset, from); // offset has type off_type

例子

// remember the current write position in mark 

ostringstream writeStr; // output stringstream

ostringstream::pos_type mark = writeStr.tellp(); // ...

if (cancelEntry)
    // return to the remembered position 
    
    writeStr.seekp(mark);

Reading and Writing to the Same File

e.g. 写一个新行 contains the relative position at which each line begins

  • 注意不用输出第一行, 因为从第一行开始的
  • offset counts 每行结尾的换行符
abcd 
efg 
hi
j

生成: 
abcd 
efg 
hi
j
5 9 12 14
// open for input and output and preposition file pointers to end-of-file

fstream inOut("copyOut", fstream::ate | fstream::in | fstream::out); 
if (!inOut) {
    cerr << "Unable to open file!" << endl;
    return EXIT_FAILURE; // EXIT_FAILURE see § 6.3.2(p. 227)

}  
// inOut is opened in ate mode, so it starts out positioned at the end position

auto end_mark = inOut.tellg();// remember original end-of-file

inOut.seekg(0, fstream::beg); // reposition to the start of the  file 

size_t cnt = 0; // accumulator for the byte count 

string line; // hold each line of input

// while we haven't hit an error and are still reading the original data

while (inOut && inOut.tellg() != end_mark
        && getline(inOut, line)) { // and can get another line of input 

    cnt += line.size() + 1;  // add 1 因为换行符
    
    auto mark = inOut.tellg(); // remember the read position

    inOut.seekp(0, fstream::end); // set the write marker to the end

    inOut << cnt; // write the accumulated length

    // print a separator if this is not the last line

    if (mark != end_mark) inOut << " ";

    inOut.seekg(mark); // restore the read position 

}
inOut.seekp(0, fstream::end);  // seek to the end

inOut << "\n";  write a newline at end-of-file


18. Tools for Large Programs

(a). Exception Handling

  1. Throwing an Exception
  2. Catching an Exception
  3. Function try Blocks and Constructors
  4. The noexcept Exception Specification
  5. Exception Specifications and Pointers, Virtuals, and Copy Control
  6. Exception Class Hierarchies

1. Throwing an Exception

  • Exception handlng allow to hand problems that arise at run time
  • one part of program detect problem and pass job of resolving to another part of program (Detecting part 不需要知道handling part)
  • exception is raised by throwing an expression
  • when throw executed, statement following throw 不被执行. 而是 control is transferred from throw to matching catch. catch 可能是同一个函数local 的 or 直接或间接called the function which exception occurred. control pass 用两个implications:
    • Functions along the call chain 也许prematurely exited(提早退出)
    • when a handler is entered, objects created along the call chain 将 destoryed.
    • a throw is like return . 通常是conditional statement or last statement in a function

Stack Unwinding

  • 当exception thrown, execeution of current function suspended and search for matching catch clause begin:
    • 如果 throw inside a try block, 检查与该 try 关联的 catch 。如果catch 匹配, 就使用该 catch 处理exception.
      • 否则. 如果该 try 嵌套在 其他的try 中, 检查 外层 try 匹配的 catch. 若无匹配 catch, 退出当前函数(current function), 在调用当前函数外层函数中( search in calling function )继续寻找
    • 如果call to the function that threw is in a try block, 继续与上面一样的操作, 若无匹配 catch , calling function is exited. search 外层的函数
    • 上面的过程叫做 stack unwinding: continue up the chain of nested function calls until a catch clause for exception is found, or main function is exited without having found a matching catch
      • 如果matching catch found, catch is entered, program continue by executing code inside catch.
      • 如果没有matching catch found, program is exited by calls the library terminate function, stops the execution of program.
  • During stack unwinding, blocks in the call chain may be exited prematurely(调用链上的语句块可能提前退出)
    • When a block is exited during stack unwinding, the compiler guarantees that objects created in that block are properly destroyed
      • 如果local object is class type, destructor 自动被call
      • As usual, compiler does no work to destroy objects of built-in type.
  • 如果exception in constructor, object 也许 partial constructor.一些member 被 initialized, but others 没有被initialized. 我们需要保证constructed member properly destroyed
  • 如果exception in destructor, exception occurs before 负责free resource 代码, free resources被跳过(will not executed). 所以要确保 resouces are properly freed when using class to control resource allocation.
    • 如果throw exception, 没有被catch, terminated is called
    • 所以never throw exceptions that destructor itself doesn’t hanlde; 如果might throw, should wrap in a try block and handle it locally to destructor
    • 实际上, 因为destructor free resources, unlikely they will throw exceptions.
    • 所有的standard library types 保证destructor not raise an exception.

The Exception Object

  • execption object: compiler use the thrown expression to copy initalize.
    • 因此, the expression in a throw 必须有 complete type
    • 如果expression has class type, destructor accessible and accessible copy or move constructor.
    • 如果expression 是array of function type, expression 被转化为pointer type
  • exception object 在space 中, guaranteed to be accessible to catch is invoked. The exception object destoryed after exception is completely handled
  • throw a pointer to a local object is error
    • 因为local object may be detoryed before 若 catch pointer points to an object in a block that is exited before the catch.
    • 同理返回a pointer to local object is error, 因为返回前, local object destoryed.
  • Expression static, compile-time type 决定了 type of exception object
    • 比如throw derefereces 一个pointer to base-class type points to derived-type object. thrown object is sliced-down; only base-class part is thrown

2.Catching an Exception

  • exception declaration 只有 exactly 一个parameter. 可以忽略name 如果 catch no need to access thrown expression
  • Type 必须是 a complete type, 可以是 lvalue reference, 不能是 rvalue reference
  • parameter in exception declaration is initialized by exception object.
    • 如果 catch parameter 是 nonreference type, then parameter in catch is copy of exception object. change parameter是local的, 不会影响exception object
    • 如果 catch parameter 是 reference type. catch parameter is another name for exception object. Changes made to parameter are made to exception object
    • 如果 catch parameter that has a base-class type 可以被initialized by an exception object derived from parameter type
      • 如果 catch parameter a nonreference type, exception object is sliced down.
      • 如果parameter is reference to a base-class type, then parameter bound by derived-to-base conversion.
    • 跟function 一样, static type of the exception declaration determines the actions that the catch may perform 比如 如果catch parameter 是 base-class type, then the catch cannot use any members that are unique to the derived type.

Finding a Matching Handler

  • catch 发现的 不一定是最匹配的,而是 first one that matches the exception at all
    • 因此, in a list of catch, the most specialized catch must appear first
    • Most derived 放前面, least derived 放后面
  • rules for exception matchces a catch exception declaration 是严格的
    • Conversions from nonconst to const are allowed. 因此throw of a nonconst oject 可以match a catch specified to take a reference to const
    • Conversion from derived type to base type are allowed.
    • Array/function convert to pointer
    • 不允许arithmetic conversion 和 conversion defined for class types
  • To catch all exceptions,use an ellipsis for the exception declaration.
    • 通常上 catch all clause catch(...) is often combination with a rethrow expression
    • 如果catch(...) combine with other catch clauses, catch-all 必须最后出现 (last), 否则any match 在 catch-all 之后的 never matched
void manip(){
    try{
        //actions that cause an exception to be thrown

    }catch(...){
        //work to partially handle the exception

    } 
}

Rethrow

  • 有时候单独catch 不能完全处理 exception. A catch may pass the exception further up the call chain to another catch by rethrowing the exception
  • A rethrow is a throw that is not followed by an expression
    • empty throw 只能出现在 in a catch or in a function called from a catch
    • rethrow 不 specify an expression
    • 如果改变parameter后 rethrow, 这些changes will be propagated only if catch exception declaration is a reference

比如下面例子只有parameter 是 reference时候, 才会更改 exception object pass up the chain

catch (my_error &eObj) {
    eObj.status = errCodes::severeErr; 
    throw; // the status member of the exception object is severeErr

} catch (other_error eObj) { 
    eObj.status = errCodes::badErr;
    throw; // the status member of the exception object is unchanged 
    
}

3. Function try Blocks and Constructors

  • 如果exception occur in consturctor initializer, catch inside constructor body can’t handle exception thrown by constructor initializer 因为 try block inside constructor body 不会in effect when exception thrown
  • try block lets us 处理 initialization phase of a constructor(or the destruction phase of a destructor) 和 constructor’s (or destructor’s) function body. 比如下面例子
  • The only way for to handle an exception from a constructor initializer is to write the constructor as a function try block.

注意:

  • keyword try appears before colon that begins constructor initializer list and before curly brace that forms constructor function body
  • try 关联的 catch 既能处理excpetion in initialization list fo from constructor body
  • 还有注意的是: exception when initializing constructor’s parameter. 是 function try block 不能handle的, function try block 只能 handle once constructor 开始execution. 这种exception 需要被 handled in caller’s context
template <typename T> Blob<T>::Blob(std::initializer_list<T> il) try :
        data(std::make_shared<std::vector<T>>(il)) { 
    /* empty body */
} catch(const std::bad_alloc &e) { handle_out_of_memory(e); }

4. The noexcept Exception Specification

  • 如果compiler know no exception thrown, perform optimization (不适用于可能throw的), noexcept specifier
    • declaration:
      • The noexcept specifier must appear on all the declarations and definition of a function or on none of them
      • noexcept 应在trailing return 之前.
      • 也可以 sepcify noexcept on declaration and definition of a function pointer.
      • 不能appear in a typedef or type alias.
      • 在const ,reference qualifier 之后, 在 final, override or virtual function =0, constructor initializer list 之前
      • compiler no check exception specifications 对于 声明 noexcept 是否throw exception 不会检查 at compiled time
        • 如果声明了noexcept does throw, terminate is called. enforcing the promise not to throw at run time
      • noxcept 应该被用于两种情况:
        • case 1: confident that function won’t throw
        • case 2: don’t know what to handle the error
      • noexcept 有个 optional bool argument. 若argument is true, function won’t throw; 若 argument is false, then function might throw.
  • noexcept Operator: unary operator(一元运算符), returns a bool rvalue constant expression 表示 whether given expression might throw. 像sizeof, noexcept 不会 evaluate its operand.
    • noexcept specifier 的argument 通常与 noexcept operator 混合使用,
    • noexcept(e) : 返回true, 如果 e have nonthrowing specification noexcept and e itself does not contain a throw. 否则 返回 false
    • often used as bool argument to noexcept exception specifier

下面例子, 我们说 recoup has a nonthrowing specification

void recoup(int) noexcept; // won't throw 

void alloc(int); // might throw

早期c++ 提供了更详细的exception specifications 可以specify the types of exceptions that function might throw.The approach never use and deprecated in current standarad. throw() 表示不throw any exceptions

void recoup(int) noexcept; // recoup doesn't throw 

void recoup(int) throw(); // equivalent declaration

noexcept optional argument

void recoup(int) noexcept(true); // recoup won't throw 

void alloc(int) noexcept(false); // alloc can throw

noexcept Operator: 下面例子如果 function g 承诺不 throw, f is nonthrowing. 若 g has no exception specifier, or has an exception specifier that allows exceptions, then f might throw

// true if calling recoup can't throw, false otherwise

noexcept(recoup(i)) 
void f() noexcept(noexcept(g())); // f has same exception specifier as g

5. Exception Specifications and Pointers, Virtuals, and Copy Control

  • 尽管noexcept 不属于 function’s type, 但 a pointer to function and the function 被指向的must have compatible specifications
    • 若 pointer nonthrowing exception specification. 我们可以用pointer only to noexcept functions
    • 若 pointer 没有nonthrowing exception specification pointer 可以指向任何function, 即使function promise not to throw的
  • 如果virtual functionnonthrowing exception specification, inherited virtual 必须都有nonthrowing exception specification. 若base 没有声明, derived function 可以声明 也可以不声明
  • 如果all class members operation and base classes promise not to throw, 那么 compiler synthesized 的copy-control member not to throw.
    • 如果any function invoked by synthesized member can throw, 则 sythesized member is noexcept(false)
  • exception specification for destructor, compiler 会synthesizes one with exception speciaition 与compiler 假设synthesized 的destructor类型一样
// both recoup and pf1 promise not to throw 

void (*pf1)(int) noexcept = recoup;

// ok: recoup won't throw; it doesn't matter that pf2 might 

void (*pf2)(int) = recoup;

pf1 = alloc; // error: alloc might throw but pf1 said it wouldn't 

pf2 = alloc; // ok: both pf2 and alloc might throw

class Base {
public:
    virtual double f1(double) noexcept; // doesn't throw virtual 
    
    int f2() noexcept(false); // can throw virtual 
    
    void f3(); // can throw
};
class Derived : public Base { 
public:
    double f1(double); // error: Base::f1 promises not to throw 
    
    int f2() noexcept(false); // ok: same specification as Base::f2 
    
    void f3() noexcept; // ok: Derived f3 is more restrictive

};

6. Exception Class Hierarchies

  • exception 只定义 copy constructor, copy-assignment operator, a virtual destructor, and a virtual member named what
    • what 返回 const char* that points to a null-terminated character array. guaranteed not to throw any exceptions.
    • what is virtual, 可以execute the version to dynamic type of exception object by reference to base type
  • exception, bad_cast, and bad_alloc classes define a default constructor.
  • runtime_error and logic_error classes no default constructor but have constructors that take a C-style character string or string argument. Those arguments are intended to give additional information about the error.
  • As the hierarchy becomes deeper, each layer becomes a more specific exception. 比如catch an object of type exception 只知道something wrong, 细节没有描述
  • runtime_error: 表示can be detected only when program is executing
  • logic_error:表示我们可以从程序代码中发现的错误

e.g. Bookstore application exception

// hypothetical exception classes for a bookstore application 

class out_of_stock: public std::runtime_error { 
public:
    explicit out_of_stock(const std::string &s): std::runtime_error(s) { }
};
class isbn_mismatch: public std::logic_error { 
public:
    explicit isbn_mismatch(const std::string &s): std::logic_error(s) { }
    isbn_mismatch(const std::string &s,
            const std::string &lhs, const std::string &rhs): 
            std::logic_error(s), left(lhs), right(rhs) { }
    const std::string left, right;
};

比如, 为 Sales_data 定义了一个复合加法运算符,当检测两个 ISBN 编号不一致时, 抛出名为 isbn_mismatch 的异常.

// throws an exception if both objects do not refer to the same book 

Sales_data&
Sales_data::operator+=(const Sales_data& rhs)
{
    if (isbn() != rhs.isbn())
        throw isbn_mismatch("wrong isbns", isbn(), rhs.isbn());
    units_sold += rhs.units_sold; 
    revenue += rhs.revenue; 
    return *this;
}

// use the hypothetical bookstore exceptions

Sales_data item1, item2, sum;
while (cin >> item1 >> item2) { // read two transactions

    try {
        sum = item1 + item2; // calculate their sum
	
    } catch (const isbn_mismatch &e) {
        cerr << e.what() << ": left isbn(" << e.left
        << ") right isbn(" << e.right << ")" << endl;
    }
}

(b), Namespace

  1. Namespace Definitions
  2. Using Namespace Members
  3. Classes, Namespaces, and Scope
  4. Argument-Dependent Lookup and Parameters of Class Type
  5. Friend Declarations and Argument-Dependent Lookup
  6. Argument-Dependent Lookup and Overloading
  7. Overloading and using Declarations, Overloading and using Directives
  8. Overloading across Multiple using Directives
  • 对于大型程序, using libraries from different vendors, some of these names will clash 冲突因为命名一样, Libraries that put names into the global namespace are said to cause namespace pollution.
    • Traditionally, programmers 避免 namespace pollution by using 很冗长function name
    • Namespace provide a much more controlled mechanism for preventing name collisions.
      • Namespace partition global namesapce.
      • A namespace is a scope. By defining library’s names inside a namespace, library authors 可以避免全局名字的限制

1. Namespace Definitions

  • definition: keyword namespace followed by namespace name. 之后是花括号括起来的 声明和定义.
  • Any declaration that can appear at global scope 可以被放进 namespace: classes, variable(with initializations), functions(with definitions), templates and other namesapces
  • a namespace name must be unique within the scope in which the namespace is defined.
  • 不可以定义namespace inside a function or class
  • A namespace scope does not end with a semicolon.
  • Each namespace is a scope. Different namespaces introduce different scopes, different namespaces may have members with the same name
    • namespace 成员被外访问时 must indicate the namespace
      • 比如 cplusplus_primer::Query q = cplusplus_primer::Query("hello");
  • Namespaces can be discontiguous: a namespace 可以被定义 in several parts
    • 因为namespace members that define casses, and declarations for functions and object 是class interface, put in header files
    • definitions of namespace member can be put in separate source files
      • need to ensure functions and other names we defined only once.
      • 可以define namespace function outside namespace, 只需加上 namespace name 和 scope operator (Once name seen, assume in scope of namesapce).
    • 定义多个, unrelated types 的 namespaces 时,should use separate files to define 表示每个类型 或者关联类型集合
  • 注意 not put a #include inside the namespace. 如果这么做表示定义header总所有名字 定义成该命名空间的成员, 比如string include in cplusplus_primer 表示 define std nested inside cplusplus_primer Error
  • Template specializations must be defined in the same namespace that contains the original template . 只要在namespace 中声明specialization, 就可以define outside namespace
  • Nested Namespaces:
    • Names declared in inner namespace hide declarations of the same name in outer namespace.
  • Inline Namespaces: 不像nested namespaces, names in inline namespace can be used as direct members of enclosing namspace (可以被外层命名空间直接使用), 使用时 不需要提供inline namespace name. 只需提供enclosing namespace name
    • keyword inline must appear on first definition of the namespace. If the namespace is later reopened, the keyword inline 可以写也可以不写
    • inline namespace 通常用于 one release of an application to next. 例如put all code from current edition into an inline namesapce. Previous version 放进 noninlined namespaces
  • Unnamed Namespaces: 在keyword namespace 后 没有name, 后紧跟curly braces 括起来的 declarations
    • Variables defined in an unnamed namespace 有 static lifetime: created before first use and destoryed when program ends
    • An unnamed namespace may be discontiguous within a given file but does not span files.
      • 每个file 有自己的unnamed namespace, 如果two files contain unnamed namespace,those namespaces are unrelated. Both unnamed namespaces can define the same name; those definitions would refer to different entitie
      • 如果一个header defines a unnamed namespace, the names in that namespace define different entities local to each file that includes the header.
    • Names defined in an unnamed namespace are used directly. 也找不到namespace name which qualify them.
    • unnamed namespace 可以 nested in another namespace. 如果 unnamed namespace nested, then names in it are accessed using enclosing namespace name(s) - inline namespace and Unnamed Namespace: 他们都是in the same scope as the scope at which unnamed namespace/inline space is defined.
    • 区别是inline namespace 自动是same scope, inline Namespace 是在 using directive 时候在same scope

Namespaces can be discontiguous:比如下面code, 可以是定义新namespace nsp or adds to existing namespace

namespace nsp { 
    // declarations

}

定义outside namespace: 与class member defined outside class类似, once fully qualified name is seen, we are in scope of namespace

cplusplus_primer::Sales_data cplusplus_primer::operator+(const Sales_data& lhs,
{ 
    //...

}

Template specializations: Template specializations must be defined in the same namespace that contains the original template

namespace std {
} template <> struct hash<Sales_data>;
// having added the declaration for the specialization to std 

// we can define the specialization outside the std namespace

template <> struct std::hash<Sales_data>
{

};
size_t operator()(const Sales_data& s) const { 
    return hash<string>()(s.bookNo) ^
    hash<unsigned>()(s.units_sold) ^
    hash<double>()(s.revenue); 
    } 
    // other members as before

};

Nested Namespaces: Names defined inside nested namespace 是local to inner namespace. Code in outer refer to a name in nested namespace only through its qualified name

namespace cplusplus_primer {
    //nested namespace: defines Query portion of the library
    
    namespace QueryLib {
        class Query { /* ... */ };
        Query operator&(const Query&, const Query&); 
    }
}

cplusplus_primer::QueryLib::Query

Inline Namespaces

inline namespace FifthEd {
    // namespace for the code from the Primer Fifth Edition

} 
namespace FifthEd { 
    // implicitly inline class 
    
    Query_base { };
    // other Query-related declarations 
    
}

namespace FourthEd {
    class Item_base {}; 
    class Query_base { }; 
    // other code from the Fourth Edition
    
}

定义 cplusplus_primer, 因为 FifthEd 是 inline, code that refers to cplusplus_primer::Query_base will get version from that namspace. 如果想得到 earlier edition, can access it as nested namespace, by using the names of all the enclosing namespaces: cplusplus_primer::FourthEd::Query_base.

namespace cplusplus_primer { 
    #include "FifthEd.h" 
    
    #include "FourthEd.h"
    
}

inline namespace member 跟定义它的enclosing scope 是 in the same scope


namespace nsp {
   inline namespace {
      int i = 5;
   }
   int i = 10;
}
cout << nsp::i << endl; //ambiguous

Unnamed Namespace

E.g1

int i; // global declaration for i

namespace {
    inti;
 } 
// ambiguous: defined globally and in an unnested, unnamed namespace 

i = 10;

unnamed namespace 可以 nested in another namespace. 如果 unnamed namespace nested, then names in it are accessed using enclosing namespace name(s)

namespace local { 
    namespace {
        int i;
    } 
} 
// ok: i defined in a nested unnamed namespace is distinct from global i 

local::i = 42;

E.g.2

void Foo() // 1

{}
namespace
{
  void Foo() // 2
  
  {}
}

namespace { // re-open same anonymous namespace

    void do_main()
    {
      Foo(); // Calls local, anonymous namespace (Foo #2).
      
      ::Foo(); // Calls the Foo in the global namespace (Foo #1).

    }
}

int main() {
    do_main();
}

E.g.3

namespace nsp {
   namespace {
      int i = 5;
   }
   int i = 10;
}
cout << nsp::i<<endl;//print 10

E.g.4, ambiguous call, 因为using directive 把所有member 展开

namespace nsp {
   namespace {
      int i = 5;
   }
   int i = 10;
}
using namespace nsp;
cout << i<<endl; //ambiguous

2. Using Namespace Members

Namespace Aliases

  • declaration begins with keyword namespace followed by alias name (别名), =, orignal namespace name
  • an error to use namspace alias if the original namespace name not defined yet.
  • 一个namespace 可以有多个 synonyms, or aliases. All the aliases and the original namespace name can be used interchangeably.
namespace cplusplus_primer { /* ... */ };
namespace primer = cplusplus_primer;

using Declarations: A Recap

  • Using Declaration only introduce one namespace member at a time
    • visible from using declaration to end of scope in which the declaration appear. 一旦scope 结束, fully qualified name(有namespace name prefix的) must be used
    • Entities with the same name defined in an outer scope are hidden
  • using declaration 可以是global, local, namespace, class scope. 在class scope 中, 只能用于refer base class member

using Directiveusing namespace namespace_name

  • Using Directive 和 Using Declaration 相同之处是: use unqualified form of a namespace name
  • Using Directive 和 Using Declaration 不同是, namespace 中所有 名字都是 visible without qualification
  • 可以用于global,local, namespace, 但是不能用于class scope
  • Providing a using directive for 多个 namespaces, such as std, 程序可能有 name collision problems

using Directives and Scope

  • using declaration 从效果看 是declare a local alias for namespace member
  • using directive 而是 lift namespace members 到 nearest scope that contains both namespace itself and using directive
    • 是nearest scope, 因为injected namespace 和现在enclosing scope name 有冲突, 因此as if appeared in nestest enclosing scope
    • local defintion 也许会hide namespace member
  • 如果header 含有 using declaration or using directive, 则会将namespace中的名字 injected into every file that include the header
    • 因为header should define only the names as part of its interface, not names used in its implemtation
    • 因此, header should not contain using directive or using declaration
  • Avoid using Directives:
    • if application use many libraries, and 名字可能有冲突, global namespace pollution. 而且Fail to compile 如果增加一个library 跟现在using directive 的有冲突
    • 而且ambiguity error 只有被用的时, 冲突爆发时 之前未被检测的错误 才被发现,
    • Better to use using declaration: Ambiguity errors caused by using declaration 被检测到 at the point of declaration not use

下面例子:

  • Assume manip define in global scope,then member of blip 就像也被declare在global scope
  • manipblip 中的 j 有冲突, use the name j, must explicitly 说明which version . Unqualified j is ambiguous
    • ::j is defined in global scope, blip::j defined in namespace
  • local declarations within manip hide namespace k. k is not ambiguous, 表示local 的
namespace blip {
    int i = 16, j = 15, k = 23;
}
int j = 0; // okay: blip 的j 隐藏在命名空间中 

void manip(){
    using namespace blip;//using directive
        
        // 如果使用了j, 将会有冲突(clash) between ::j and blip::j 

        ++i; //set blip::i to 17 

        ++j; // error ambiguous global j or blip::j?

        ++::j // okay: set global j to 1

        ++blip::j; //okay set blip::j to 16

        int k = 97; //local k hides blip::k

        ++k; // set local k to 98
	
}

3. Classes, Namespaces, and Scope

  • Name Lookup follows normal lookup rules: search looks outward through enclosing scope: 由内向外一次查找每个外层作用域, 而且只有在使用电之前声明的名字才被考虑
    • first lookup member, then class (including base classes), then enclosing scopes(可能是多个namespace)
    • 除了class member lookup 看class 内部的, 因为class member function may defined inside class, 永远是向上lookup
      • E.g.2 f2 not compile, h not defined yet.
      • E.g.2 h inside f3 is okay, 因为f3 defined after A::h
    • qualified name 说明了检查scope 顺序, reversed order
      • E.g.2 A::C1::f3: 先查 function f3, then class C1, then namespace A

E.g.1

namespace A {
    int i;
    namespace B {
        int i; // hides A::i within B

        int j;
        int f1()
        {
            int j; // j is local to f1 and hides A::B::j
            
            return i; // returns B::i
        
        }
    } // namespace B is closed and names in it are no longer visible
    
    int f2() {
        return j; // error: j is not defined
   
    }
    int j = i; // initialized from A::i

}

E.g. 2, 注意 f3定义 可以通过lookup A 发现 h

namespace A {
    int i;
    int k;
    class C1 {
        public:
            C1(): i(0), j(0) { } // ok: initializes C1::i and C1::j

            int f1() { return k; } // returns A::k

            int f2() { return h; } // error: h is not defined

            int f3();
        private:
        int i; // hides A::i within C1

        int j;
    };
    int h = i; // initialized from A::i
    
}
// member f3 is defined outside class C1 and outside namespace A

int A::C1::f3() { return h; } // ok: returns A::h

4. Argument-Dependent Lookup and Parameters of Class Type

比如 operator>>(std::cin, s); defined in string library and defined in std namespace, 但是can call operator>> without std:: qualifier and without using declaration

std::string s;
std::cin >> s;
//等价于

operator>>(std::cin, s);
  • When pass a class type to a function, compiler searches the namespace which argument’s class is defined in addition to normal scope lookup.
    • This exception also apply to calls that pass pointers or references to a class type
  • 上面例子中: compiler see operator>>, look for a matching function in current scope, 因为 >> expression has parameter of class type, look for cin and sdefined 的 namespace (istream and string), then compiler find the string outuput operator function
  • 如果没有上面的例外, 需要using delcaration or have to use function-call notation including namespace qualifer, 增加了使用IO难度
    • using std::operator>>;
    • or std::operator>>(std::cin, s); : explicitly use std::>>

Lookup and std::move and std::forward

  • 如果程序定义了 name also in library. 下面两个one is true
    • normal overloading determinte which call
    • application 使用自己定义的, never ues library version
  • Both move and forwardparameter type as rvalue reference which can match any type. 因此 自己定义 move no matter what type parameter has, 都会collide library move, (forward也是这样)
  • 因为 move and forward are specialized type manipulations. override behavior of these functions chance very small, And collision very likely. so using std::move rather than move 表示we use version from library

下面例子, 自己定义version 更specialized

struct str {
   str(string a) {
      cout << "in " << endl;
   }
   friend str&& move(str && a) {
      cout << " in " << endl;
   }
};
str a = string("dog");
auto b = move(a); //print in

5.Friend Declarations and Argument-Dependent Lookup

  • when class declares a friend, friend declaration does not make the friend visible
  • undeclared class or function that is first named in a friend declaration is assumed to be a member of the closest enclosing namespace。 一个未声明的类或者函数 第一次出现在友元声明中, 则认为它是最近外层命名空间的成员

下面例子: f and f2 are members of namespace A.

  • Through argument-dependent lookup, we can call f even if there is no additional declaration for f
    • f takes an argument of class type. and f is implicitly declared in the same namespace as C -f2 has no parameter, will not be found
namespace A {
    class C {
    // two friends; neither is declared apart from a friend declaration
    
    // these functions implicitly are members of namespace A
    
    friend void f2(); // won't be found, unless otherwise declared
    
    friend void f(const C&); // found by argument-dependent lookup
    };
}

int main()
{
    A::C cobj;  
    f(cobj); // ok: finds A::f through the friend declaration in A::C
    
    f2(); // error: A::f2 not declared

}

6. Argument-Dependent Lookup and Overloading

  • 根据Argument-Dependent Lookup (include namespace where argument’s class is defined). 会影响 如何确定 candidate set.
    • Any 一样名字 functions in those namespace as called function 被added to candidate set. They are added, even though they are not visible at point of the call

比如下面: display 的 argument is Bulk_item, search namespace where Bulk_item and its base class Quote declared

namespace NS {
    class Quote { /* ... */ };
    void display(const Quote&) { /* ... */ }
}
// Bulk_item's base class is declared in namespace NS

class Bulk_item : public NS::Quote { /* ... */ };

int main() {
    Bulk_item book1;
    display(book1);
    return 0;
}

7. Overloading and using Declarations, Overloading and using Directives

  • using declarations:
    • using declaration declares a name, not a specific function
    • When we write a using declaration for overloaded functions, all the versions of that function are brought into the current scope.
      • 引入所有版本 ensure interface not violated. 因为作者定义了different functions for a reason, 如果用户选择性忽略一些, can leading surprising behavior
    • 如果using declaration in local scope, hides existing declarations in outer scope
    • 如果using declaration introduece 的function 跟现在scope 已有的function name and parameter list 一样, using declaration is in error. Otherwise, defines additonal overloaded instances and increase candidate functions set
  • using directives:
    • A using directive lifts the namespace members into the enclosing scope. 如果 namespace placed scope 有一样命名的 function, then the namespace member is added to the overload set
    • 不同于 using declaration, It is not an error if a using directive introduces a function 有一样的 parameters as existng function. No problem 除非we try to call the function without specifying the one from namespace or from current scope

using declaration 是 declare name

using NS::print(int); // error: cannot specify a parameter list

using NS::print; // ok: using declarations specify names only

Overloading and using Directives:

namespace libs_R_us {
    extern void print(int);
    extern void print(double);
}
// 普通声明

void print(const std::string &);
// this using directive adds names to the candidate set for calls to print:

using namespace libs_R_us;

// 此时 print 调用函数 candidate set 包括

// print(int) from libs_R_us

// print(double) from libs_R_us

// print(const std::string &) declared explicitly

void fooBar(int ival)
{
    print("Value: "); // calls global print(const string &)
    
    print(ival); // calls libs_R_us::print(int)
    
}

8. Overloading across Multiple using Directives

  • 如果存在多个 using directive. names from each namespace become part of candidate set

下面例子中: even though print(int), print(double), print(long double) 来自于 different namespace scopes. 但都是 main 中 print 的 候选集

namespace AW {
    int print(int);
} 
namespace Primer {
    double print(double);
} 
// using directives create an overload set of functions from different namespaces u

sing namespace AW;
using namespace Primer;
long double print(long double); 
int main() {
    print(1); // calls AW::print(int) 
    
    print(3.1); // calls Primer::print(double) return 0
    
}

(c). Multiple/Virtual Inheritance

  1. Multiple Inheritance
  2. Conversions and Multiple Base Classes
  3. Class Scope under Multiple Inheritance
  4. Virtual Inheritance
  5. Constructors and Virtual Inheritance

1. Multiple Inheritance

  • 如果继承access specifier omitted, specifier defaults to private for class, and public for struct
  • in derivation list, 自能include classes that have been defined 而且 不能是 defined asfinal
  • 没有限制 number of base classes from which a class can be derived. 但是一个base 只能出现一次在 derivation list
  • Derived constructors initialize all base classes(subobjects)
  • 与single base class(§ 15.2.2)类似, Derived constructor 的 constructor initializer 只能initialize direct base classes
  • direct base class 初始顺序与 derivation list 相关的, order 与 constructor initializer list 无关
  • Destructors are always invoked in reverse orderfrom constructors are run
    • Derived class destructor 只负责 clean resources allocated by that class only. Destructor 自动call, synthesized destructor has empty function body.
  • 对于inherit constructors from one or more of its base classes. Error to inherit 有一样parameter list constructor from more than one base class. 见下面例子
    • 解决这样问题 是在 class 中 定义own version of that constructor
  • Copy/Move:
    • 如果derived class use synthesized version of copy, move / assign, base parts 被自动被 copied, moved, or assigned.
    • In synthesized copy-control members, base class is implicitly constructed, assigned, or destroyed
class Bear : public ZooAnimal {
class Panda : public Bear, public Endangered { /* ... */ };   
Conceptual Structure of a Panda Object

Derived constructor 只 initialize direct base classes

// explicitly initialize both base classes 

Panda::Panda(std::string name, bool onExhibit)
      : Bear(name, onExhibit, "Panda"), Endangered(Endangered::critical) { }

// implicitly uses the Bear default constructor to initialize the Bear subobject

Panda::Panda()
: Endangered(Endangered::critical) { }

上面class direct base 初始化顺序, 与 derivation list顺序一致, derivation list : class Panda : public Bear, public Endangered: 顺序如下

  1. ZooAnimal initialized first
  2. Bear second direct base is initialized next
  3. Endangered most derived part is initialized last
  4. Panda 最后

被cdostryed 的顺序

  1. ~Panda
  2. ~Enadangered
  3. ~Bear
  4. ~ZooAnimal

Error to inherit 有一样parameter list constructor from more than one base class: 因为Base1Base2 都有 constructor takes const std::string& as parameter. Error to inherit from constructor

struct Base1 {
    Base1() = default; 
    Base1(const std::string&); 
    Base1(std::shared_ptr<int>);
};
struct Base2 {
    Base2() = default; 
    Base2(const std::string&); 
    Base2(int);
};
// error: D1 attempts to inherit D1::D1 (const string&) from both base 

classes struct D1: public Base1, public Base2 {
    using Base1::Base1; // inherit constructors from Base1

    using Base2::Base2; // inherit constructors from Base2 
    
};

避免上面的错误, 必须定义own version of constructor

struct D2: public Base1, public Base2 {
    using Base1::Base1; // inherit constructors from Base1 
    
    using Base2::Base2; // inherit constructors from Base2 
    
    // D2 must define its own constructor that takes a string

    D2(const string &s): Base1(s), Base2(s) { }
    D2() = default; // needed once D2 defines its own constructor

};

Copy and Move Operations for Multiply Derived Classes

顺序(copy-assignment 也类似): 下面的copy control: 会invoke Bear, then Zoo constructor before excecuting Bear copy constructor. Then execute Endangared copy constructor, 最后run Panda copy constructor

Panda ying_yang("ying_yang");
Panda ling_ling = ying_yang; // uses the copy constructor

2.Conversions and Multiple Base Classes

  • Derived-to-base conversion: converting to each base class is equally good..
    • 比如 most derived -> derived 和 most derived -> base conversion 在compiler 看来一样好
  • 与single inheritance一样, static type of the object, pointer, or reference determines which members we can use
    • 比如使用ZooAnimal pointer, operation 定义在 ZooAnimal 中可以用, Bear-specific, Pandas-specific, Endangered-specific operations invisible
//operations that take references to base classes of type Panda 

void print(const Bear&);
void highlight(const Endangered&);
ostream& operator<<(ostream&, const ZooAnimal&);

Panda ying_yang("ying_yang");
print(ying_yang); // passes Panda to a reference to Bear 

highlight(ying_yang); // passes Panda to a reference to Endangered

cout << ying_yang << endl; // passes Panda to a reference to ZooAnimal

Converting to each base class is equally good.

void print(const Bear&);
void print(const Endangered&);
Panda ying_yang("ying_yang"); 
print(ying_yang); // error: ambiguous

Object, reference, pointer 的 Static type 决定可以call 哪个member

例子: Virtual Functions in the ZooAnimal/Endangered Classes

Function Class Defining Own Versions
print ZooAnimal::ZooAnimal
Bear::Bear
Endangered::Endangered
Panda::Panda
highlight Endangered::Endangered
Panda::Panda
toes Bear::Bear
Panda::Panda
cuddle Panda::Panda
destructor ZooAnimal::ZooAnimal
Endangered::Endangered
Bear *pb = new Panda("ying_yang");
pb->print(); // ok: Panda::print()

pb->cuddle(); // error: not part of the Bear interface

pb->highlight(); // error: not part of the Bear interface 

delete pb; // ok: Panda::~Panda()

Endangered *pe = new Panda("ying_yang"); 
pe->print(); // ok: Panda::print()

pe->toes(); // error: not part of the Endangered interface 

pe->cuddle(); // error: not part of the Endangered interface 

pe->highlight(); // ok: Panda::highlight()

delete pe; // ok: Panda::~Panda()

3. Class Scope under Multiple Inheritance

  • Single Inheritance: lookup searching up the inheritance hierachy until given nmae is found. 先找most derived class, 再找 least derived class, 最后找base class, 自下而上的查找
    • Name in derived class hide use of that name inside base.
  • Multipe Inheritance: lookup happens simultaneously among all the direct base classes.
    • 如果name is found more than one base class, Unqualified uses of that name are ambiguous
      • 但如果想避免ambigous, must specify which version we want to use
      • 比如 use a name through Panda(object, pointer, reference), 则会parallel 查找 Endangered and Bear/ZooAnimal subtrees. 如果发现 name in more than one subtree, ambiguous
        • 不管是第几个level in both subtree, 只要找到more in subtree, 就是ambugious. 比如 function defined in ZooAnimal(base in one subtree) 和 Endangered(least derived in another subtree), 就是ambugious
    • Name Lookup before type checking
      • 即使不同 base classes 中funtion name 一样 但是有 different parameter lists, derived class call will be ambiguious
      • function were private in one base class and public or protected in another base class, derived class call 是 ambiguious

例子, both ZooAnimalEndangered define a member max_wiehgt, 则下面call 是error. 如果Pandas object 不 call max_weihgt, ambiguity is avoided. 也可以避免 ambiguity, 当specifiy which version to run

double d = ying_yang.max_weight();
double d = ying_yang.ZooAnimal::max_weight(); //okay

double d = ying_yang.Endangered::max_weight(); //okay

best way to avoid potential ambiguities is to define a version of the function in the derived class

double Panda::max_weight() const
{ 
    return std::max(ZooAnimal::max_weight(), Endangered::max_weight());
}

Name Lookup before type checking:

class Base1{
public:
   void get() {}
};
class Base2 {
public:
   void get(int i) {}
};
class nsp : public Base1, public Base2{
public:
};

nsp aa;
aa.get(1); //ambiguous

4. Virtual Inheritance

  • virtual inheritance lets a class specify that it is willing to share its base class(share a single instance of base class ).
    • The shared base-class subobject is called a virtual base class.
    • 不管virtual base appear 几次in inheritance hierarchy, the derived object 只contain only one shared subobject for that virtual base class
  • Virtual derivation affects the classes that subsequently derive from a class with a virtual base; 并不会影响 derived class 本身.
    • 比如下面例子中,Bear and Raccoon class (intermediate base class) 需要specify virutal on their derivation from ZooAnimal,
  • public virtual or virtual public 顺序都可以
  • 即使base class is virtual, 也有derived-to-base conversion, 可以用virtual base pointer/reference 绑定 derived class
  • Member visibility:
    • virtual base 因为只有一个shared subobject, 所以 virtual base 的member can be accessed directly 并且 unambiguous.
    • 如果 virtual base member只被一个 derivation path override, 可以被accessed directly 且 unambiguous
    • 如果 virtual base member 被多个 derivation path override,则通常上 derived class 需要定义自己version
    • 例子说明 B defined member x, D1 and D2 都virtual inherits from B, D inherits from D1 and D2. 如果想 use x through D object, 三种可能:
      1. D1, D2 没有自己的 x, access B 的 x no ambiguity
      2. D1 or D2 定义了(override) x, access D1 or D2 x no ambiguity
      3. D1 and D2 都定义了(override) x, access x: ambiguity : 解决方法是在 D中定义自己的 x(可以call D1, D2, or B 的x by explicitly specify version 在自己的 x中 )

例子一: istream and ostream classes each inherit from a common abstract base class 是 basic_ios(hold stream’s buffer and manages stream’s condition state). 而iostream继承了 both istream and ostream 默认情况下, 如果一个base class 在derived classes 出现多次, derived oject will have more than one subobject of that type. 对于iostream 如果两个 basic_ios, 一个iostream 不会用一样的buffer for both reading and writing.

例子二:比如pandas 是属于 Raccoon 类 还是 Bear 有争议,就两个class 都继承

因为 RaccoonBear inherited virtually from ZooAnimal, 所以 Pandas 只有一个 ZooAnimal base part. virtual specifier 表示一种愿望, share a single instance of the named base class within a subsequently derived class.

// the order of the keywords public and virtual is not significant

class Raccoon : public virtual ZooAnimal { /* ... */ }; 
class Bear : virtual public ZooAnimal { /* ... */ };

class Panda : public Bear,
        public Raccoon, public Endangered {
};

virtual base 可以有 derived-to-base conversion

void dance(const Bear&);
void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&); Panda ying_yang;
dance(ying_yang); // ok: passes Panda object as a Bear

rummage(ying_yang); // ok: passes Panda object as a Raccoon 

cout << ying_yang; // ok: passes Panda object as a ZooAnimal

5. Constructors and Virtual Inheritance

  • In a virtual derivation, the virtual base is initialized by the most derived constructor
    • 上面例子中, Panda constructor controls how ZooAnimal base class is initialized, 即使 ZooAnimal is not a direct base of Pandas
  • object 有 virtual base 的 construction order 不同于 normal, virtual base 最先初始化, using initializer in constructor of most derived class. Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy. Then 根据derivation list order 初始化. 我们例子中when Panda object is created, 顺序如下
    1. ZooAnimal part constructed first, using initializers specified in Panda constructor initializer list.
      • 注: 如果 ZooAnimal 没有explicitly initialize ZooAnimal base class, then ZooAnimal default constructor is used.如果没有default constructor, code is error
    2. Bear part constructed next
    3. Raccoon part constructed next
    4. Endangered next
    5. Panada part 最后constructed
  • 对于多个virtual base, 初始化顺序由他们 appear in derivation list 从左向右顺序决定
  • synthesized copy / move construtor, sythesized assignment operator 顺序与constructor 顺序一致
  • Destructor 顺序与 constructor 顺序相反

比如normal initialization rules 当不同virtual inheitance, Both Bear and Raccoon would initialize the ZooAnimal part of a Panda object(a base class initialize more than once).

每个class 都有可能成为 “most derived” object, the constructor 就必须initialize virtual base, 例如 当 Bear (or Raccoon ) object, no further derived type. Bear(Raccoon) constructors directly initialize their ZooAnimal base. 当Panda是 most derived class, Panda的 constructor initializes ZooAnimal. 即使 ZooAnimal 不是 direct base of Panda.

//normal inheritance

Bear::Bear(std::string name, bool onExhibit): 
    ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit) : 
    ZooAnimal(name, onExhibit, "Raccoon") { }


Panda::Panda(std::string name, bool onExhibit) : 
    ZooAnimal(name, onExhibit, "Panda"),
    Bear(name, onExhibit), Raccoon(name, onExhibit),
    Endangered(Endangered::critical), sleeping flag(false) { }

多个virtual base 的顺序由 它们出现在 derivation list 顺序决定, 比如下面例子, TeddyBear 有两个virtual base ZooAnimalToyAnimal

class Character { /* ... */ };
class BookCharacter : public Character { /* ... */ };
class ToyAnimal { /* ... */ };
class TeddyBear : public BookCharacter,
    public Bear, public virtual ToyAnimal { /* ... */ };

初始化顺序

ZooAnimal(); // Bear's virtual base class

ToyAnimal(); // direct virtual base class

Character(); // indirect base class of first nonvirtual base class

BookCharacter(); // first direct nonvirtual base class 

Bear(); // second direct nonvirtual base class

TeddyBear();// most derived class


19. Specialized Tools and Techniques

(a). Memory Allocation

  1. Overloading new and delete
  2. malloc and free Functions
  3. Placement new Expressions

1. Overloading new and delete

  • new .
    • This function allocates raw, untyped memory large enough to hold an object (or an array of objects).
    • Then runs constructor to construct the object(s) from specified initializers.
    • 最后 return a pointer to newly allocated and constructed object is
  • delete:
    • destructor is run on the object pointer points or on the elements in the array to which arr points.
    • Next, the compiler frees the memory by calling a library function named operator delete or operator delete[].
  • 可以定义自己的 new and delete, we take over responsibility for all dynamic memory allocation. Functions 必须correct
    • 可以定义在global scope or as member functions
    • 当compiler 发现 new or delete, 如果object class type, compiler 首先looks class scope 包括 base classes. 否则, look in global scope. . 如果没有找到, use library version
    • 可以用scope operator to force new or delete to use specfic version. 比如 ::new look only in global scope rather than class scope
  • Library 定义了8个 overloaded versions of operator new and delete. 前四个有可能throw bad_alloc exception. 后四个support nonthrowing versions of new
    • nothrow_t 是struct 定义在 new header(also defines a const object named nothrow, user pass nonthrowing version of new)
    • delete must not throw an exception. by noexcept exception specifier
    • 如果定义下面8个, 必须定义在global scope or class scope.
      • 当定义在class member, these operator are implicitly static(不同显示声明它static)
      • static 原因: new and delete functions 必须是 static 因为they are used either before object is constructed or after it has been destroyed. 因此newdelete 不能manipulate member data
  • new
    • operator new used when we allocate an object, operator new [] is called when we allocate an array.
    • operator new or operator new [] function must have a return type of void*first argument 必须是 size_t, size_t 不能有default argument.
    • 当call opeartor new, initialize size_t parameter with the number of bytes require to hold an object.
    • 当call operator new[], it passes the number of bytes required to store an array of all given elements
    • operator new 可以定义 additional parameter, 这种expression must use placement form of new 比如 int* a = new (noexcept) int;, 不可以定义成下面的格式:
      • void *operator new(size_t, void*); Error
      • 因为这种形式是 reserved for use by the library 不能重新定义
  • delete:
    • 必须有 void return and first parameter of type void* (initialize with a pointer to the memory to free)
    • operator delete or operator delete[] 当被定义成class member, function 可以有第二个parameter of type size_t. 初始值是the size in bytes addressed by first parameter.
      • size_t parameter is used when delete object that are part of inheritance hierarchy.
      • 如果base class has a virtual destructor, size_t取决于 dynamic type of object deleted pointer points.
    • operator delete 也是由 dynamic type 决定的
  • 与overloading 不同, 不像 operator=, 没有overload new or delete expressions. In fact, we cannot redefine the behavior of the new and delete expressions.
    • new : always executes by calling an operator new to obtain memory then construct an object in memory
    • delete expression always executes by destroying an object then calling operator delete to free memory used by an object
    • 通过提供定义 operator new and operator delete, 可以change how memory is allocated. 可是, 不能改变basic meaning of new and delete operator
  • 自己定义了 operator new and operator delete. functions 必须 allocate and deallocate memory. 即使define these functions in order to use a specialized memory allocator, 也是有用的 for testing purposes to allocate memory
// these versions might throw an exception

void *operator new(size_t); //allocate an object

void *operator new[](size_t);//allocate an array

void operator delete(void*) noexcept; //free an object 

void operator delete[](void*) noexcept; //free an array 


// versions that promise not to throw; see § 12.1.2 (p. 460)

void *operator new(size_t, nothrow_t&) noexcept; 
void *operator new[](size_t, nothrow_t&) noexcept; 
void operator delete(void*, nothrow_t&) noexcept; 
void operator delete[](void*, nothrow_t&) noexcept;
// new expressions

string *sp = new string("a value"); // allocate and initialize a string

string *arr = new string[10]; // allocate ten default initialized strings

delete sp; // destroy *sp and free the memory to which sp points 

delete [] arr; // destroy the elements in the array and free the memory

2. The malloc and free Functions

  • malloc and free inherits from C. 定义在 cstdlib
    • malloc takes a size_t 说明 how many bytes to allocate. returns a pointer to memory it allocated , or 0 if was unable to allocate the memory.
    • free takes a void* that is a copy of a pointer that was return from malloc and returns the associate memory to the system.
      • free(0) has no effect

A simple way to write operator new and operator delete as follow :

void *operator new(size_t size) { 
    if (void *mem = malloc(size)) 
        return mem;
    else
        throw bad_alloc();
} 
void operator delete(void *mem) noexcept { free(mem); }

3. Placement new Expressions

  • 与allocator 不同, 没有construct function to construct objects in memory allocated by operator new. Use placement new form of new to construct an object
    • can pass an address, place_address must be a pointer
    • initiailizers provide(possible empty) comma-separated list construct newly allocated object
  • 当called with an address and no argument, placement new uses operator new(size_t, void*) to “allocate” its memory(不许overloaded 这个function). This function not allocate any memory, simply returns its pointer argument
  • placement new allow to construct an object at a specific, prepalocated memory address
  • placement 和 allocator cosntructor 不同是:
    • the pointer pass to construct must point to space allocated by the same allocator object
    • pointer pass to placement new need not point to memory allocated by operator new
  • 就像placement new 像使用 allocate, an explicit call to a destructor is analogous to calling destroy
    • destory Calling a destructor destroys an object but does not free the memory.

placement new epxression has form

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }

explicit call destructor: invoke destructor directly, destructor preceded by a tilde (~).

string *sp = new string("a value"); // allocate and initialize a string

sp->~string();

(b). Run-Time Type Identification

  • Run-time type identification (RTTI) is provided through two operators:
    • typeid operator, returns the type of given expression
    • dynamic_cast operator: safely converts a pointer or reference to a base type into a pointer or reference to a derived type
  • 当appiled to pointers / references to types that have virtual functions, these operator use dynamic type of the object to which pointer or reference is bound
  • RTTI should be used with caution. When possible, better to define virtual function rather than to take over managing the types directly.
    • 如果不能使用virtual, can use onf of the RTTI operator. 但是 use these operators is more error-prone than virtual. programmer must know to which type oject should be cast and must check cast was performed successfully.
  1. The dynamic_cast Operator
  2. The typeid Operator
  3. Using RTTI
  4. The type_info Class

1. The dynamic_cast Operator

A dynamic_cast has the following form:

dynamic_cast<type*>(e) 
dynamic_cast<type&>(e) 
dynamic_cast<type&&>(e)
  • type must be class type and 通常含有 virtual fucntions
  • 第一个情况, e must be a valid pointer, 第二个情况, e must be valid lvalue, 第三个 e must not be lvalue
  • In all cases, e must be either a class type that is publicly derived from target type, a public base class of the target type, or same target type. 如果 e 满足这些types, cast succeed. 否则 cast fails.
    • if a dynamic_cast to a pointer type fails, result is 0.
    • if a dynamic_cast to a reference type fails, operator throws an exception of type bad_cast
  • can do a dynamic_cast on null pointer. result is a null pointer of the requested type

Pointer-Type dynamic_casts

假设Base is a class with 至少一个 virtual function and Derived publicly derived from Base. Have a pointer to Base bp, cast it at runtime to a pointer to Derived .

  • 如果 bp points to Derived object, cast initialie dp to point to the Derived object to which bp points. Safe io use Derived operations inside if
  • 否则, result of cast is 0, then condition fails, else process appropriate to Base
  • Best Practice dp defined inside the condition. do the cast and check as a single operation. 而且 if cast fails, then unbound pointer is not available for use in subsequent code
if (Derived *dp = dynamic_cast<Derived*>(bp))
{
    // use the Derived object to which dp points
    
} else { // bp points at a Base object

    // use the Base object to which bp points 

}

Reference-Type dynamic_casts

  • differ from a dynamic_cast to a pointer type in how it signals that an error occurrred. 因为没有 null reference
    • 当cast to reference type fails, cast throws a std::bad_cast exception, defined in typeinfo library header
void f(const Base & b){
    try {
        const Derived &d = dynamic_cast<const Derived&>(b); 
        // use the Derived object to which b referred  

    } catch (bad_cast) {
    // handle the fact that the cast failed 
    
    }
}

2. The typeid Operator

  • has the form typeid(e):
    • e is any expression or a type name
    • result is a reference to a const object of type_info(defined in typeinfo header), or a type publicly derived from type_info
    • typeid 可以被用于expressions of any type. 像平常一样, top-level const is ignored.
    • 当expression is reference, typeid returns type to which reference refers.
    • 当apply to array or function, standard conversion to pointer not done. if we take typeid(a) and a is an array, result describes an array, not pointer
    • 当operand is not class type or class without virtual functions, typeid operator 表示 static type of operand.
    • 当operand 是lvalue of a class type that 定义了至少一个virtual function, type is evaluate at run time
    • Whether typeid requires a run-time check determines whether the expression is evaluated.
      • evaluate expression 只当 type has virtual functions
      • 如果type has no virtuals, typeid returns the static type of expression. compiler knows the static type without evaluating the expression.
      • 如果dynamic type 不同于static type, expression must be evaluated (at run time) to determine the resulting type
        • typeid(*p). 如果p is a pointer to a type no virtual functions, p no need to be valid pointer
        • typeid(*p), if p is a null pointer, throws a bad_typeid exception
    • The typeid of a pointer (as opposed to the object to which the pointer points) returns the static, compile-time type of the pointer.

例子, 用typeid compare the types of two expressions or to compare the type of an expression to a specific type. 注意一点是 operands to typeid are deferenced pointer *bp not bp

Derived *dp = new Derived;
Base *bp = dp; // both pointers point to a Derived object 

// compare the type of two objects at run time

if (typeid(*bp) == typeid(*dp)) {
    // bp and dp point to objects of the same type

} 
// test whether the run-time type is a specific type

if (typeid(*bp) == typeid(Derived)) {
    // bp actually points to a Derived 

}

下面条件 compare type Base* to type Derived. 尽管 pointer points at object of class type that has virtual functions, the pointer itself is not a class-type object. The type Base* is evaluated at compile time. type is unequal to Derived, condition always fail 不管 type of the object which bp points

// test always fails: the type of bp is pointer to Base 

if (typeid(bp) == typeid(Derived)) {
    // code never executed 
    
}

3. Using RTTI

implement equality == operator to compare two objects. define a set of virtual functions on each hierarchy level 和 parameter 是 reference to base 的缺陷:

  • 如果parameter 是 reference to base(static type 决定可以call 哪些member), cannot compare member specific to derived class
  • 不能compare objects of different type. 比如compare base type with derived type

用 RTII, define an equality operator with parameter reference to base-class type.

  • use typeid to verify operands have the same type, 如果operands differ, the == return false, 否则will call a virtual equal function.
  • 每个class 定义自己的equal to compare the data of its own type. operator take Base& parameter but will cast (dynamic_cast) operand to its own type before doing the comparison

下面例子中 equal dynamic_cast always succeed, the function is called only testing two operands are the same type. 同时 cast is nesscessary 从而 function can access derived members of right-hand operand

class Base {
friend bool operator==(const Base&, const Base&);
public:
    // interface members for Base

protected:
    virtual bool equal(const Base&) const;
    // data and other implementation members of Base

};
class Derived: public Base { 
public:
    // other interface members for Derived 

protected:
    bool equal(const Base&) const;
// data and other implementation members of Derived 

};

bool operator==(const Base &lhs, const Base &rhs)
{
    // returns false if typeids are different; otherwise makes a virtual call to equal

    return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
    //如果 lhs Base type, then Base::equal is called 

    //如果 lhs Derived type, then Derived::equal is called 

}

bool Derived::equal(const Base &rhs) const{
    // we know the types are equal, so the cast won't throw
    
    auto r = dynamic_cast<const Derived&>(rhs);
    // do the work to compare two Derived objects and return the result

}

4. The type_info Class

  • type_info class definition varies by compiler. 但是标准库保证class defined in typeinfo header 并且 class provide at least operations list 下面表中
  • The class provides a public virtual destructor, because intended as a bass class
  • When compiler want to provide additional type information, normally does in a class derived from type_info
  • no type_info default constructor, copy/move constructor / assignment operators are defined as deleted
    • cannot define, copy, assign objects of type type_info
    • The only way to create a type_info object is through typeid operator
  • name member function returns a C-style character string for the name of type represented by type_info object. value used for given type 取决于 compiler and not required to match the type names as used in program
    • The only guarantee for name: it returns a unique string for each type
Operations on type_info
Syntax Desciption
t1 == t2 Returns true if type_info objects t1 and t2 refer to the same type, false otherwise
t1 != t2 Returns true if type_info objects t1 and t2 refer to different types, false otherwise
t.name() Returns a C-style character string that is a printable version of the type name. Type names are generated in a system-dependent way
t1.before(t2) Returns a bool that indicates whether t1 comes before t2. The ordering imposed by before is compiler dependent (before 采取的顺序关系依赖于compiler)
t arr[10];
Derived d;
Base *p = &d;
cout << typeid(42).name() << ", "
    << typeid(arr).name() << ", "
    << typeid(Sales_data).name() << ", "
     << typeid(std::string).name() << ", " 
    << typeid(p).name() << ", "
    << typeid(*p).name() << endl;

//output exeucted on our machine, gernerate output

i, A10_i, 10Sales_data, Ss, P4Base, 7Derived

(c). Enumerations

  • Enumerations let us group together sets of integral constants
  • like class, each enumeration defines a new type
  • Enumerations are literal types
  • two kinds of enumerations: scoped and unscoped
    • scope enumeration using enum class or ( equivalently enum struct) followed by the enumeration name + comma-separated list of enumerators enclosed in curly braces + ;
      • Names of enumerators (枚举成员名字) inaccessible outside the scope of enumeration. Access 需要 enumeration name + scope operator
      • scope enumerators 不会自动 convert to integral type
    • unscoped enumeration by omitting the class (or struct). 而且The enumeration name is optional
      • 只能define objects only as part of the enum definition. can provide a common-separated list of declarators 在 }; 之前, 见下面例子
      • Enumerator names are placed into the same scope as the enumeration itself. 可以直接access if in same scope 不需要scope operator
      • object of unscope enumeration type 自动convert to integral type. They can be used where an integral value is required
  • Enumerator value need not to be unique. 如果omit initializers, by default enumerator values start at 0 and each enumerator 比前一个 大 1
  • Enumerators are const, and if initialized, their initailizers must be constant expresssion. 可以用enumerator where a constant expression is required.
    • 例如, define constexpr variables of enumeration type
    • 可以 use enum in switch statement and use value of enumerators as case labels.
    • 也可以 use an enumeration type as a nontype template parameter
    • 可以 initialize class static data members of enumeration type inside class definition(只有是constant expression 时候才可以在class 内部initialize)
  • 只要 enum is named, can define and initalize. An enum object 只能被 initialized or assigned by one of its enumerators or by another object of the same enum type
    • 即使integral value 跟 enumerator 有一样的值, 也不能用它initialize / convert to enum
    • 但是 可以pass unscoped enum to function with integral type parameter. enum value promote to int or larger integral type, 取决于enumeration underlying type
  • Although each enum defines a unique type, it is represented by one of the built-in integral type, specify that type 在 enum name 之后 with a colon.
    • 如果没有指定 underlying type, default scoped enum have int as underlying type. There is no default for unscoped enum. all we know 是 underlying type 是足够大 hold enumerator values.
    • 当underlying type specified (including implicitly specified for a scoped enum). It is an error enumerator value 大于 that type
    • sepecify the underlying type 让程序确保 不同环境(implementation)中 generate the same code when compile it
  • Forward declarations for enumerations: 必须 specify (implicitly or explicitly) the underlying size(类型) of the enum
    • 因为unscope enum 没有 default size, 所以unscoped 必须specify size(type).
    • scope enum 可以without specifying a size, implicitly defined as int
    • enum declaration and defintion must match, size of one enum 必须一样 for all declarations and definition
    • 不可以declare a name as unscope enum in one context and redeclare it as scoped enum later

scope enumeration 例子 : open_modes that has three enumerators: input, output, and append.

enum class open_modes {input, output, append};

unscope enumeration

enum color {red, yellow, green}; // unscoped enumeration 

// unnamed, unscoped enum

enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10};
//define object

enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10} a, b;

Scope

enum color {red, yellow, green}; // unscoped enumeration

enum stoplight {red, yellow, green}; // error: redefines enumerators 

enum class peppers {red, yellow, green}; // ok: enumerators are hidden

color eyes = green; // ok: enumerators are in scope for an unscoped enumeration

peppers p = green; // error: enumerators from peppers are not in scope

                    // color::green is in scope but has the wrong type 

color hair = color::red; // ok: we can explicitly access the enumerators

peppers p2 = peppers::red; // ok: using red from peppers

下面例子, intTyp and shortTyp`, an enumerator value need not be unique

enum class intTypes {
    charTyp = 8, shortTyp = 16, intTyp = 16,
    longTyp = 32, long_longTyp = 64
};

因为 enumerator 是 constant expression, 可以 define constexpr variables of enumeration type

constexpr intTypes charbits = intTypes::charTyp;

Like Classes, Enumerations Define New Types

open_modes om = 2; // error: 2 is not of type open_modes

om = open_modes::input; // ok: input is an enumerator of open_modes

int i = color::red; // ok: unscoped enumerator implicitly converted to 

int int j = peppers::red; // error: scoped enumerations are not implicitly converted

Specifiy size of an enum

enum intValues : unsigned long long {
    charTyp = 255, shortTyp = 65535, intTyp = 65535, longTyp = 4294967295UL,
    long_longTyp = 18446744073709551615ULL
};

Forward Declarations for Enumerations: 必须specify the underlying size. 因为unscope enum 没有 default size, 所以unscoped 必须specify size.

// forward declaration of unscoped enum named intValues

enum intValues : unsigned long long; // unscoped, must specify a type 

enum class open_modes; // scoped enums can use int by default

Parameter Matching and Enumerations:

integral type cannot conver to enum

enum Tokens {INLINE = 128, VIRTUAL = 129};
void ff(Tokens); 
void ff(int); 
int main() {
    Tokens curTok = INLINE;
    ff(128); // exactly matches ff(int) 
    
    ff(INLINE); // exactly matches ff(Tokens) 
    
    ff(curTok); // exactly matches ff(Tokens) 
    
    return 0;
}

can pass unscoped enumeration object / enumerator to integral type parameter. enum value promotes to int or larger integral type.

下面例子中, enum Tokens 只有两个 enumerators, 大的value是 129, That value 可以表示为 unsigned char. 许多compiler use unsigned char as underlying type for Tokens. Regardless of its underlying type, objects and enumerators of Tokens promote to int, Enumerators and value of enum type 不会 promote to unsigned char 即使 enumerators value fit 用 unsigned char 储存

void newf(unsigned char); void newf(int);
unsigned char uc = VIRTUAL; 
newf(VIRTUAL); // calls newf(int)

newf(uc); // calls newf(unsigned char)

(d). Pointer to Class Member

  1. Pointers to Data Members
  2. Pointers to Member Functions
  3. Using Member Functions as Callable Objects
  • A pointer to member can point to a nonstatic member of a class.
    • 通常pointer points to an object, A pointer to member identifies a member of a class, not an object of class
    • static class members are not part of any object, no special syntax is needed. Pointer to static members are ordinary pointer
  • a pointer to member 含有 both type of class and type of member of that class
    • Initialize pointer to member 不用 指定 an object that member belongs. When use it, supply the object whose member we use
class Screen { 
public:
    typedef std::string::size_type pos;
    char get_cursor() const { return contents[cursor]; } 
    char get() const;
    char get(pos ht, pos wd) const;

private:    
    std::string contents; 
    pos cursor;
    pos height, width;
};

1. Pointers to Data Members

  • declare a pointer to member using a * to indicate declaring a pointer.
  • Unlike ordinary pointers, a pointer to member also incorporates the class that contains the member.
  • 因此用 classname::* 之前 表示 pointer can point to a member of classname
  • when initialize or assign a pointer to member, that pointer not yet point to any data. It identifies a specific member but not the object that members belong.
    • initialize: apply address-of operator(&) not to an object in memory but to a member of the class
  • Supply object when dereference the pointer to member,
    • .* and ->* let us supply an object and dereference the pointer to fetch a member of that object
    • 如果a pointer to member 的 member 是 private, the use of pointer must inside a member or friend of class, 否则是error
      • 因为一些 member are private, can’t get a pointer to data member directly. Instead would define a function to return a pointer to that member: 可以定义成static
      • 见下面例子

比如: pdata is a pointer to a member of class Screen that has type const string, 只是data member 是 const, 但是Screen 对象是不是 const 无关

// pdata can point to a string member of a const (or non const) Screen object

const string Screen::*pdata;

当initialize a pointer to member, 需要指所指成员, 例如, we can make pdata point to the contents member of an unspecified(非特定) Screen object as follow:

pdata = &Screen::contents;`

Under new standard, 最简单的办法是 declare a pointer to member is to use auto or decltype:

auto pdata = &Screen::contents

Using a Pointer to Data Member

下面例子 .* and ->* performs two actions:

  1. They dereference the pointer to member to get the member that we want
  2. fetch that member from an object (.*) or through a pointer (->*)
Screen myScreen, *pScreen = &myScreen;
// .* dereferences pdata to fetch the contents member from the object myScreen 

auto s = myScreen.*pdata;
// ->* dereferences pdata to fetch contents from the object to which pScreen points 

s = pScreen->*pdata

A Function Returning a Pointer to Data Member (比较重要例子)

Read return type from right to left, data returns a pointer to a member of class Screen that is const string. Function body applies the address-of operator to the contents member, functions 返回 a pointer to the contents member of Screen. 注意, pdata points to a member of class Screen but not to actual data. To use pdata, 必须 bind it to an object of type Screen

class Screen { 
public:
    // data is a static member that returns a pointer to member 
    
    static const std::string Screen::*data()
    { return &Screen::contents; } // other members as before

};

//we call data, we get a pointer to member

// data() returns a pointer to the contents member of class Screen 

const string Screen::*pdata = Screen::data();

// fetch the contents of the object named myScreen 

auto s = myScreen.*pdata;

2. Pointers to Member Functions

// pmf is a pointer that can point to a Screen member function that is const 

// that returns a char and takes no arguments

auto pmf = &Screen::get_cursor;
  • Like pointer to data member, pointer to function member declared using classname::*
  • specifies the return type and parameter list
  • If the member function is a const member or a reference member, must include the const or reference as well in pointer declaration
  • If the member function overloaded, 必须 declaring the type explicitly. 不能用auto
  • Unlike ordinary function pointers, no automatic conversion from a member function and a pointer to that member, 必须用 address-of operator
  • pointer to function member can be used as return type or parameter (可以有default parameter)

如果member function overload, 必须declare the type explicitly, 比如下面例子, 根据function pointer 确定point to which member function.

char (Screen::*pmf2)(Screen::pos, Screen::pos) const; 
pmf2 = &Screen::get;

注意 上面的 parentheses around Screen:: is essential due to precedence, 如果没有parantheses, compiler treats following as function declaration: try to define an ordinary function p that returns a pointer to a member of class Screen taat has type char. 因为是ordinary function, declaration 不能有 const qualifier

// error: nonmember function p cannot have a const qualifier 

char Screen::*p(Screen::pos, Screen::pos) const;

不想普通的函数pointer, no automatic conversion between a member function and a pointer to that member

// pmf points to a Screen member that takes no arguments and returns char

pmf = &Screen::get; // must explicitly use the address-of operator

pmf = Screen::get; // error: no conversion to pointer for member functions

Using a Pointer to Member Function

  • As a pointer to a data member, we use .* or -> operator to call a member function through a pointer to member
  • (myScreen->*pmf)() 外层括号不可少, 因为precedence of call operator higher than precedence of the pointer to member operators
    • 没有 paratheses, myScreen.*pmf() would be interpreted as myScreen.*(pmf()): use function pmf return value as operand of pointer-to-member operator .* 因为 pmf not function, error
Screen myScreen,*pScreen = &myScreen;
// call the function to which pmf points on the object to which pScreen points

char c1 = (pScreen->*pmf)();
// passes the arguments 0, 0 to the two-parameter version of get on the object myScreen

char c2 = (myScreen.*pmf2)(0, 0);

Using Type Aliases for Member Pointers

Type aliases make code that uses pointers to members much easier to read and write.

// Action is a type that can point to a member function of Screen 

// that returns a char and takes two pos arguments

using Action =
    char (Screen::*)(Screen::pos, Screen::pos) const;

//with Type alias, can simplify the definition of a pointer to get 

Action get = &Screen::get; // get points to the get member of Screen

Like any other parameter, a pointer-to-member parameter can have a default argument, a pointer to member function 作为 member parameters, 可以 passing either a pointer or address of an approriate member function.

// action takes a reference to a Screen and a pointer to a Screen member function 

Screen& action(Screen&, Action = &Screen::get);

Screen myScreen;
// equivalent calls:

action(myScreen); // uses the default argument 

action(myScreen, get); // uses the variable get that we previously defined 

action(myScreen, &Screen::get); // passes the address explicitly

Pointer-to-Member Function Tables

One common use for function pointers and for pointers to member functions is store in a function table. 比如 a class has several members of same type, function table can be used to select one.

例子, Screen class is extended to contain several member functions, 每个 moves the cursor in particular direction

class Screen { 
public:
    // other interface and implementation members as before
    
    // cursor movement functions

    Screen& home(); 
    Screen& forward();
    Screen& back(); 
    Screen& up(); 
    Screen& down();
};

we might want to define a move that can call any of these functions and perform indicated action. To support new function, add a static member an array of pointers Menu: will pointers to each of cursor movement functions. move takes an enumerator and calls 对应 functions: the Menu element indexed by cm is fetched. That element is a pointer to a member function of Screen class. 根据 this 所指对象 调用该元素所指的成员函数

class Screen { 
public:
    // other interface and implementation members as before
    
    // Action is a pointer that can be assigned any of the cursor movement members
    
    using Action = Screen& (Screen::*)();
    // specify which direction to move; enum see § 19.3 (p. 832)

    enum Directions { HOME, FORWARD, BACK, UP, DOWN };
    Screen& move(Directions); 
private:
    static Action Menu[]; // function table 

};


Screen::Action Screen::Menu[] = { &Screen::home, &Screen::forward,
        &Screen::back, &Screen::up,  &Screen::down};


Screen& Screen::move(Directions cm)
{
    // run the element indexed by cm on this object
    
    return (this->*Menu[cm])(); // Menu[cm] points to a member function

}

Screen myScreen;
myScreen.move(Screen::HOME); // invokes myScreen.home 

myScreen.move(Screen::DOWN); // invokes myScreen.down

3. Using Member Functions as Callable Objects

  • 因为 call through a pointer to member function, must use .* or ->* operators to bind the pointer to a specific object. 所以, 不像 ordinary function pointers, a pointer to member is not a callable object()not support function-call operator)
    • 所以不能directly pass a pointer to a member function to an algorithm
  • 方法一: use library function template
    • member function execute is passed to the implicit this parameter. 当 want to use function to generate a callable for a member function, have to translate the code to make implicit parameter explicit
    • function first parameter 必须是 represent the (normally implicit) object on which the member will be run. The signature we give to function must specify whether the object will be passed as a pointer or a reference
  • 方法二: Using mem_fn to Generate a Callable
    • function must supply the call signature of member we want to call.
    • let compiler deduce the member’s type by using another library facility mem_fn like function, 定义在 functional header
    • function 一样地方: mem_fn: generate a callable object from a pointer to member
    • function 不同地方: mem_fn deduce the type of callable from the type of the pointer to member
    • The callable generated by mem_fn can be called on either an object or a pointer
  • 方法三: Using bind to Generate a Callable
    • function 一样, must make 通常implicit parameter (that represents object will operate) explicit .
    • Like mem_fn, the first argument to callable generated by bind can be a pointer or a reference

不能directly pass a pointer to a member function to an algorithm, 比如 find first empty string in a vector<string>, 之前call won’t work.

auto fp = &string::empty; // fp points to the string empty function 

// error: must use .* or ->* to call a pointer to member

find_if(svec.begin(), svec.end(), fp);

上面code won’t compile, 因为 code inside find_if executes a statement something like

// check whether the given predicate applied to the current element yields true 

if (fp(*it)) // error: must use ->* to call through a pointer to member

Using function to Generate a Callable

obtain a callable from a pointer to member function is by using the library function template, 下面例子告诉compiler, empty is a function that can be called with string and return bool.

function<bool (const string&)> fcn = &string::empty; 
find_if(svec.begin(), svec.end(), fcn);

When a function object holds a pointer to a member function, the function class knows that it must use the appropriate pointer-to-member operator to make the call. 可以想象find_if will have code like

// assuming it is the iterator inside find_if, so *it is an object in the given range 

if (fcn(*it)) // assuming fcn is the name of the callable inside find_if

when function execute using proper pointer-to-member operator, function transform this call into

// assuming it is the iterator inside find_if, so *it is an object in the given range 

if (((*it).*p)()) // assuming p is the pointer to member function inside fcn

When the callable is a member function, the signature’s first parameter must represent the (normally implicit) object on which the member will be run. The signature we give to function must specify whether the object will be passed as a pointer or a reference.

When we defined fcn, we knew that we wanted to call find_if on a sequence of string objects. 因此we asked function to generate a callable that took string objects. 比如下面 vector 保存是 pointer, function expect a pointer

vector<string*> pvec;
function<bool (const string*)> fp = &string::empty; // fp takes a pointer to string and uses the ->* to call empty 

find_if(pvec.begin(), pvec.end(), fp);

Using mem_fn to Generate a Callable

use mem_fn(&string::empty) to generate a callable object that takes a string and return bool

find_if(svec.begin(), svec.end(), mem_fn(&string::empty));

The callable generated by mem_fn can be called on either an object or a pointer. 可以想象mem_fn generates a callable with an overloaded function call operator: one takes a string* and other a string&

auto f = mem_fn(&string::empty); // f takes a string or a string* 

f(*svec.begin()); // ok: passes a string object; f uses .* to call empty 

f(&svec[0]); // ok: passes a pointer to string; f uses .-> to call empty

3. Using bind to Generate a Callable

// bind each string in the range to the implicit first argument to empty 

auto it = find_if(svec.begin(), svec.end(),
    bind(&string::empty, _1));

auto f = bind(&string::empty, _1);
f(*svec.begin()); // ok: argument is a string f will use .* to call empty 

f(&svec[0]); // ok: argument is a pointer to string f will use .-> to call empty

(e). Nested Classes

  • Nested class(nested type): A class can be defined within another class.
    • 通常 define implementatin classes
  • Nested class 是 independent class and 与外层enclosing class没什么关系
    • no connection between objects of enclosing class and object of nested class.
      • A nested-type object 只含有 members defined inside nested type.
      • an object of enclosing class has only members defined by enclosing class. 没有data members of any nested classes
    • nested class is visible within enclosing class scope but not outside the class. nested class 不会和其他scope 中同一个名字冲突
    • Enclosing class 没有特殊访问权限 对于 nested class, and nested class has no special access to members of its enclosing class.
  • Access:
    • 如果 a nested class define public part of enclosing class, can be used anywhere.
    • A nested class define in protected part of enclosing class, accessible only by enclosing class, its friends, and its derived classes.
    • A private nested class of enclosing class: accessible only to members and friends of enclosing class
  • 如果 actual definition of nested class 定义(可以outside class define) 被看见之前, nested class is an imcomplete type
  • nested class cannot access enclosing name, but can access global name, 见下面例子
  • nested class static member, defintion ouside scope of nested class
  • Name Lookup: nested class has additional enclosing class scope to search. 而且 nested class can access enclosing class scope member wihout specifiying the enclosing class.
    • 只能access type names, static members, and enumerators from the enclosing class without specify enclosing class object

Declaring a Nested Class

下面例子: 如果 QueryResult 用作其他purpose 没有实际意义, 所以定义为 TextQuery 的 member, 因为QueryResult as TextQuery, we must declare QueryResult before we use it as the return type for query member

class TextQuery { 
public:
    class QueryResult; // nested class to be defined later
    
    // other members as in § 12.3.2 (p. 487) 

};

Defining a Nested Class outside of the Enclosing Class

唯一改变是 不需要定义 QueryResult::line_no, 因为 QueryResult can access that name direct from TextQuery (enclosing class)

// we're defining the QueryResult class that is a member of  class TextQuery 

class TextQuery::QueryResult {
    // in class scope, we don't have to qualify the name of the QueryResult parameters

    friend std::ostream&
                print(std::ostream&, const QueryResult&);
public:
    // no need to define QueryResult::line_no; a nested class can use a member 
    
    // of its enclosing class without needing to qualify the member's name
    
    QueryResult(std::string,
                std::shared_ptr<std::set<line_no>>,
    std::shared_ptr<std::vector<std::string>>); 
    // other members as in § 12.3.2 (p. 487)

};

define QueryResult constructor: need to provide enclosing class and nested class name

// defining the member named QueryResult for the class named QueryResult 

// that is nested inside the class TextQuery 

TextQuery::QueryResult::QueryResult(string s,
    shared_ptr<set<line_no>> p,
    shared_ptr<vector<string>> f): sought(s), lines(p), file(f) { }

static member

// defines an int static member of QueryResult

// which is a class nested inside TextQuery

int TextQuery::QueryResult::static_mem = 1024;

Name Lookup in Nested Class Scope: 因为return type not yet in scope of the class, we start by noting out function returns a TextQuery::QueryResult . 但是在function 内部, 可以 refer to QueryResult directly. 只有typedef is typename 可以被access without specifiy object

TextQuery::QueryResult
TextQuery::query(const string &sought) const
{
    // we'll return a pointer to this set if we don't find sought

    static shared_ptr<set<line_no>> nodata(new set<line_no>); 
        // use find and not a subscript to avoid adding words to wm!

    auto loc = wm.find(sought);

    if (loc == wm.end())
        return QueryResult(sought, nodata, file); // not found

    else
        return QueryResult(sought, loc->second, file);
}
int x,y; // globals

class enclose { // enclosing class

    int x; // note: private members

    static int s;
 public:
    struct inner { // nested class

        void f(int i) {
            x = i; // Error: can't write to non-static enclose::x without instance

            int a = sizeof x; // Error until C++11,

                              // OK in C++11: operand of sizeof is unevaluated,

                              // this use of the non-static enclose::x is allowed.

            s = i;   // OK: can assign to the static enclose::s

            ::x = i; // OK: can assign to global x

            y = i;   // OK: can assign to global y

        }
        void g(enclose* p, int i) {
            p->x = i; // OK: assign to enclose::x

        }
    };
};

Nested class can be forwarded-declared and later defined

class enclose {
    class nested1; // forward declaration

    class nested2; // forward declaration

    class nested1 {}; // definition of nested class

};
class enclose::nested2 { }; // definition of nested class

The Nested and Enclosing Classes Are Independent

因为 nested class QueryResult 中不含有 enclosing class QueryResult 成员, 所以用 enclosing class member 初始化 QueryResult 作为return statement

return QueryResult(sought, loc->second, file);

(f). Union: A Space-Saving Class

  • 可以有multiple data members, 但是在任意时刻只有一个 member have a value.
    • when a value is assigned to one member of union, all other members become undefined.
  • The amount of union storage 至少是跟 largest data member 一样大
  • Like any class, a union defines a new type.
  • A union cannot have a reference member
    • under new standard, class types that have constructors or destructors 可以是 union member
  • A union 可以specify to make members public, private, protected.
    • Like struct, 默认 member of a union are public.
  • A union 可以定义 member functions, including constructors and destructors.
  • union 不能inherit from another class 或者 作为 base class.
    • 所以 a union 不能有virtual function
  • union offer a convenient way to represent a set of mutually exclusive values of different types. 见下面例子
  • Like built-in types, by default union are uninitialized (未初始化的). can explicitly initialize a union like explicitly initialize aggregate classes by enclosing the initializer in curly braces
    • If an initializer is present, it is used to initialize the first member. 下面例子
    • assign value to a data member of union object 使其他 member undefined. 因此 when we use a union, must know what type of value currently stored in union. 如果retieving or assigning value stored in union through wrong member can lead to a crash or other incorrect program behavior
  • Anonymous unions: is unamed union 在右括号和分号之间没有任何声明, 一旦定义了 anonymous union compiler 自动create an unnamed object of newly defined union type
    • member of anonymous union 可以被 access directly 在in anonymous union 定义的 scope .
    • An anonymous union cannot have private or protected members, nor can define member functions
  • for built-in type member,
    • can ordinary assignment to change value of union hold
    • compiler synthesize memberwise default constructor or copy-control member
  • for union that have members of nontrivial class,
    • switch to class type 需要run constructor, switch from class to 其他值, must destory that member by running destructor
    • for member class that defines its default constructor or 至少一个 copy-control member. compiler 把union自己定义的 copy member 定义为 delete
      • 比如 string 定义了5个copy-control and default constructor, 如果union 包含string, 却没有定义 default constructor and copy control member, compiler synthesize 这些missing member as deleted

Defining a union

比如have a process that handles different kinds of numeric or character data. 下面例子deinfes a union that can hold a value that is either a char, an int, or a double

// objects of type Token have a single member, which could be of any of the listed types

 union Token {
    // members are public by default

    char cval; 
    int ival; 
    double dval;
};

Using a union Type

Like built-in types, by default union are uninitialized (未初始化的), 可以像初始化aggregate class 一样初始化 union. If an initializer is present, it is used to initialize the first member, 所以 first_token gives a value to cval member

Token first_token = {'a'}; // initializes the cval member

Token last_token; // uninitialized Token object

Token *pt = new Token; // pointer to an uninitialized Token object

using member access operator to access member of union type object

last_token.cval = 'z'; 
pt->ival = 42;

Anonymous union: 成员可以在被定义的scope 直接使用

union { // anonymous union 

    char cval;
    int ival
    double dval;
    
}; // defines an unnamed object, whose members we can access directly

cval = 'c'; // assigns a new value to the unnamed, anonymous union object 

ival = 42; // that object now holds the value 42

unions with Members of Class Type

  • 因为 complexity invovled in constructing / destroying members of class type, union with class-type 通常 embeded inside another class
    • 比如we add string member to union, 定义 union as anonymous union and make it member of a class Token. The Token class will manage union member
    • to keep track of what type value union holds, 定义separate object known as discriminate
    • 下面例子定义 enumeration type to keep track union member state (discriminate)
    • class need 定义 default constructor, copy-control members and assignment operator that assign a value of one union types to union member

下面例子

  • 定义一个 unnamed, unscoped enumeration, define tok as unnamed enum type
    • use tok as our discriminant. 当 union hold int, tok have value INT, when union has string, tok is STR
  • class default constructor initialize discriminant and union hold int as 0
  • 因为union 的string with destructor, must define our own destructor to destroy string member.
    • 不像class type, 因为union member class 有destructor, union destructor synthesized deleted. string member 不会自动被destroyed. 因为destructor no wya to know which type union hold, cannot know which member to destroy.
    • 自己定义destructor check if object being destroyed hold string. if so, destructor explicitly call string destructor to free memory. if hold built-in type, no work for union
class Token{
public:
    // copy control needed because our class has a union with a string member

    // defining the move constructor and move-assignment operator is left as an exercise

    Token(): tok(INT), ival{0} { }
    Token(const Token &t): tok(t.tok) { copyUnion(t); }
    Token &operator=(const Token&);

    // if the union holds a string, we must destroy it; 
    
    ~Token() { if (tok == STR) sval.~string(); }
    // assignment operators to set the differing members of the union
    
    Token &operator=(const std::string&);
    Token &operator=(char);
    Token &operator=(int);
    Token &operator=(double);
private:
    enum {INT, CHAR, DBL, STR} tok; // discriminant
    
    union { // anonymous union
        
        char cval;
        int ival;
        double dval;
        std::string sval;
    }; // each Token object has an unnamed member of this unnamed union type
    
    // check the discriminant and copy the union member as appropriate
    
    void copyUnion(const Token&);
};

If current value in union is string, 必须destroy that string before assign new value. after assign value, update discriminat. 对string assignment, 如果已经是string, can use normal string assignment operator. 否则 no exisiting string object, must construct new string in the memory that holds the union by using replacement new to construct a string at location which sval resides. initialize string as copy of string parameter. Then update discriminant and return

Token &Token::operator=(int i)
{
    if (tok == STR) sval.~string(); // if we have a string, free it

    ival = i; // assign to the appropriate member
    
    tok = INT; // update the discriminant
    
    return *this;
}
Token &Token::operator=(const std::string &s)
{
    if (tok == STR) // if we already hold a string, just do an assignment

        sval = s;
    else
        new(&sval) string(s); // otherwise construct a string

    tok = STR; // update the discriminant

    return *this;
}

Managing Union Members That Require Copy Control

  • call copyUnion from copy constructor, union member 被默认初始化, 表示first member of union initialized. 因为string 不是第一个成员, 所以we know 第一个成员不是string, copyUnion assume that if parameter hold string, copyUnion 必须construct its own string
  • 但对于assignment operator, it is possible that union 已经hold a string, 要分三种情况, 两边都是string, 只有左侧是string(需要), 只有右侧是string
void Token::copyUnion(const Token& t)
{
    switch (t.tok) {
        case Token::INT: ival = t.ival; break;
        case Token::CHAR: cval = t.cval; break;
        case Token::DBL: dval = t.dval; break;
        // to copy a string, construct it using placement new; see (§ 19.1.2 (p.
        824))

        case Token::STR: new(&sval) string(t.sval); break;
    }
}
Token &Token::operator=(const Token &t)
{
    // if this object holds a string and t doesn't, we have to free the old string
    
    if (tok == STR && t.tok != STR) sval.~string();
    
    if (tok == STR && t.tok == STR)
        sval = t.sval; // no need to construct a new string
    
    else
        copyUnion(t); // will construct a string if t.tok is STR
    
    tok = t.tok;
    return *this;
}

(g). Local Classes

  • local class: A class can be defined inside function body.
    • only visible in the scope in which it is defined.
  • All members, including functions, of a local class must be completely defined inside the class body. 因此, local classes less useful than nested classes.
    • code may difficult for reader to understand
  • local class 不能定义 static data members,
  • Name Lookup:
    • Local Classes只能使用 type names, static varaibles and enumerators defined within enclosing local scope
    • Local Classes 不能使用 variables from function scope where class defined
    • enclosing function 不能access private member of local class. local class 可以 make enclosing function friend,
    • 通常上local class 定义member as public, 因为a local class 已经encapsulated within scope of function. 进一步 encapsulation 没什么必要
    • 查找顺序与其他class 一样: named used in member defintion can appear anywhere in class. 如果 a name not found in class member, then search enclosing scope then search out of the scoping enclosing the function
  • possible have nested class inside a local class, nested class 的 defintion 可以appear otuside local-class body, 但是nested class 必须被定义在 same scope as local class defined (比如 不能定义在function 外面),
    • 因为nested class in local class 也是local class so all members of nested class 必须defined inside the body of nested class

Name Lookup:

int a, val;
void foo(int val)
{
    static int si;
    enum Loc { a = 1024, b };
    // Bar is local to foo
    
    struct Bar {
        Loc locVal; // ok: uses a local type name

        int barVal;
        void fooBar(Loc l = a) // ok: default argument is Loc::a

        {
            barVal = val; // error: val is local to foo

            barVal = ::val; // ok: uses a global object

            barVal = si; // ok: uses a static local object

            locVal = b; // ok: uses an enumerator

        }
    };
}

Nested class: As usual, when define member outside a class, must indicate scope of name. Bar::Nested

void foo()
{
    class Bar {
    public:
        class Nested; // declares class Nested

    };
    // definition of Nested

    class Bar::Nested {
    // ...
    
    };
}

(h). Inherently Nonportable Features

  • inherently nonportable feature: 是指因机器而定的特性 (machine specific).
    • 当 program 含有 nonportable features moved from 一个machine 到另一个machine, 通常需要重写程序
    • Size of arithmetic types 是一个, vary across the machines
  1. Bit-fields
  2. volatile Qualifier
  3. Linkage Directives: extern “C”

1. Bit-fields

  • A class can define a non-static data member as bit-field. 一个 bit-field holds a specified number of bits.
  • Bit-fields are normally used when a program needs to pass binary data to another program or to a hardware device.
  • The memory layout of a bit-field is machine dependent (bit-fields 在内存中布局与机器相关)
  • A bit-field must have integral or enumeration type
    • 通常用 unsigned type to hold a bit-field, 因为behavior of signed bit-field is implementation defined.
  • 定义方法是 : member_name : val, val 是 constant expression specifying the number of bits
  • Bit-fields defined in consecutive order within the class body are, if possible, packed within adjacent bits of the same integer, thereby providing for storage compaction. 在类内部连续定义的 bit-field 可能被压缩到同一个整数的相邻位, 从而提供存储压缩.
    • 例如下面例子: 连续定义的5个bit-field 可能存储到 single unsigned int. Whether and how the bits are packed into the integer is machine dependent
  • address-of operator(&) 不能用于 bit-field. 因为no pointers referring to class bit-fields
  • class defined bit-field member 通常有 set of inline member functions to test and set value of bit-field

例子:

typedef unsigned int Bit;
class File {
    Bit mode: 2; // mode has 2 bits

    Bit modified: 1; // modified has 1 bit

    Bit prot_owner: 3; // prot_owner has 3 bits

    Bit prot_group: 3; // prot_group has 3 bits

    Bit prot_world: 3; // prot_world has 3 bits

    // operations and data members of File

public:
    // file modes specified as octal literals; see § 2.1.3 (p. 38)

    enum modes { READ = 01, WRITE = 02, EXECUTE = 03 };
    File &open(modes);
    void close();
    void write();
    bool isRead() const;
    void setWrite();
};

Using Bit-fields

bit-field is accessed in the same way as other data members of a class

void File::write()
{
    modified = 1;
    // . . .

}
void File::close()
{
    if (modified)
    // . . . save contents

}

bit-fields with more than one bit are usually manipluated using built-in bitwise operators

File &File::open(File::modes m)
{
    mode |= READ; // set the READ bit by default

    // other processing
    
    if (m & WRITE) // if opening READ and WRITE
    
    // processing to open the file in read/write mode
    
    return *this;
}

class defined bit-field member 通常有 set of inline member functions to test and set value of bit-field

inline bool File::isRead() const { return mode & READ; }
inline void File::setWrite() { mode |= WRITE; }

2. volatile Qualifier

  • precise meaning of volatile is inherently machine dependent 需要read compiler documentation.
    • Programs use volatile must be changed when move to new machine or compiler
  • program deal with hardware 通常有data elements whose value controlled by processes outside direct control of the program
    • 例如 a program contain a variable updated by system clock.
  • A object should be declared as volatile when its value changed outside the control or detection of program
  • volatile is directive to compiler that should not perform optimizations on such object
  • volatile used the same as const qualifer, additional modifier to a type
    • no interaction between the const and volatile type qualifiers. A type can be both const and volatile
    • 一个class member 可以被定义成 volatile. Only volatile member functions may be called on volatile objects
    • 可以declare pointers that are volatile, pointers to volatile objects, and volatile pointer point to volatile objects
      • 与const 一样, 只能assign the address of a volatile object only to a pointer to volatile
  • One important difference between const and volatile:
    • synthesized copy/move and assignment operators cannot be used to initialize or assign from a volatile object
      • 因为synthesized member parameter 是 nonvolatile, 不能 bind a nonvolatile to volatile ( nonvolatile <– volatile Error)
    • 如果想要一个volatile objects to be copied, moved, or assigned, 必须 define its own version of copy / move operations. 那么parameter 是 const volatile reerences,
      • 尽管可以定义拷贝 volatile object, 是不是copy volatile 对象有意义, 不同程序使用volatile 目的各不相同, 对这个问题回答与使用目的相关
volatile int display_register; // int value that might change

volatile Task *curr_task; // curr_task points to a volatile object

volatile int iax[max_size]; // each element in iax is volatile

volatile Screen bitmapBuf; // each member of bitmapBuf is volatile

volatile pointer, pointers to volatile objects, and volatile pointer point to volatile objects.

volatile int v; // v is a volatile int

int *volatile vip; // vip is a volatile pointer to int

volatile int *ivp; // ivp is a pointer to volatile int

// vivp is a volatile pointer to volatile int

volatile int *volatile vivp;

int *ip = &v; // error: must use a pointer to volatile

*ivp = &v; // ok: ivp is a pointer to volatile

vivp = &v; // ok: vivp is a volatile pointer to volatile

因为synthesized version parameter 是 nonvolatile, 所以不能copy/assign volatile object, 所以需要自己定义copy-control member, parameter type 是 const volatile reference

class Foo {
public:
Foo(const volatile Foo&); // copy from a volatile object

// assign from a volatile object to a nonvolatile object

Foo& operator=(volatile const Foo&);
// assign from a volatile object to a volatile object

Foo& operator=(volatile const Foo&) volatile;
    // remainder of class Foo

};

3. Linkage Directives: extern “C”

  • C++ 有时需要call functions written in another porgramming language (c++). The name of that function must be declared, specify return type and parameter list. Compiler checks calls to functions written in another language in the same way handles ordinary C++ functions. 但是生成的代码有所区别. C++ use linkage directives to indicate langauge used for non-C++ function
  • Mixing C++ with code written in any other language, including C, 需要有权access该语言compiler 且与 C++ 兼容

Declaring a Non-C++ Function

  • linkage directive 可以两种形式: single or compound.
  • linkage directive 不能出现在 class or function definition. The same linkage directive must appear on every declartion of a function

linkage directive 形式是 extern + string literal(the language in which function is written) + ordinary function declaration. 下面例子 A compiler 需要 support linkage directive for C. A compiler may provide linkage specifications for other languages 比如 extern "Ada", extern "FORTRAN"

// illustrative linkage directives that might appear in the C++ header <cstring> 

// single-statement linkage directive

extern "C" size_t strlen(const char *); 
// compound-statement linkage directive

extern "C" {
    int strcmp(const char*, const char*); 
    char *strcat(char*, const char*);
}

Linkage Directives and Headers

  • can give the same linkage to several functions at once 用curly braces.
    • These braces serve to group the declarations to which the linkage directive applies.
    • 若linkage directive not apply, braces are ignored, function names within braces are visiable as if declared outside the braces
  • muliple declaration form can be applied to entire header 比如下面例子 C++ cstring header
    • 当一个 #include directive enclosed in compound-linkage directive, header 中所有functions 被认为是由 linkage directive 语言编写的
  • linkage direcitive can be nested, 比如一个header contains a function with its own linkage directive, lnikage of that function is unaffected.
  • C++ 继承的 C library 是可以定义成 C 函数但并非必须, C++ implementation 决定 whether to implement C library functions in C or C++
// compound-statement linkage directive

extern "C" {
#include <string.h> // C functions that manipulate C-style strings 

}

Pointers to extern “C” Functions

  • The language in which a function is written is part of its type. 因此 function declaration with a linkage directive 必须用一样的 linkage directive pointer
    • pointer to function written in other language must be declared with the same linkage directive as function
    • 比如 extern "C" void (*pf)(int);, function call compiler 认定 call to a C function. A pointer to C function cannot be initialized or assigned to point to a C++ function
      • It’s error to assign two pointers with different linkage directive

对于下面例子 pf1 = pf2, 有些compiler 会接受,但严格上讲是 illegal的

void (*pf1)(int); // points to a C++ function 

extern "C" void (*pf2)(int); // points to a C function 

pf1 = pf2; // error: pf1 and pf2 have different types

Linkage Directives Apply to the Entire Declaration

  • When use linkage directive, it applies to return type and as a parameter type for function and function pointers
  • 如果想pass a pointer to a C function to a C++ function, 必须用type alias, 因为linkage directive applies to all functions in declartion

比如下面例子, the linage directive applies to parameter, a function pointer ; when call f1, must pass the name of C function or a pointer to a C function

// f1 is a C function; its parameter is a pointer to a C function 

extern "C" void f1(void(*)(int));

pass a pointer to a C function to a C++ function, must type alias

// FC is a pointer to a C function

extern "C" typedef void FC(int);
// f2 is a C++ function with a parameter that is a pointer to a C function 

void f2(FC *);

Exporting Our C++ Functions to Other Languages

  • By using the linkage directive on a function definition, we can make a C++ function available to another language program
    • When the compiler generates code for this function, it will generate code appropriate to the indicated language.
  • 值得注意是: 可被共享的函数 reurn type and parameter type are often constrained. 比如, 我们不能pass objects a nontrivial C++ class to C program. C program won’t know about constructors, destructors, other class -specific operations
  • To allow the same source file to be compiled under either C or C++, the preprocessor defines _ _cplusplus (two underscores) when we compile C++. Using this variable, we can conditionally include code when we are compiling C++:
// the calc function can be called from C programs

extern "C" double calc(double dparm) { /* ... */ }
#ifdef __cplusplus // ok: we're compiling C++

extern "C"
#endif
int strcmp(const char*, const char*);

Overloaded Functions and Linkage Directives

  • 如果target language 支持overloaded function, it is likely that a compiler that implements linkage directives for that language would support overloading of these function from C++.
    • interaction between linkage directives and function overloading 取决于 target language.
  • C language 不支持 function overloading, C linkage directive can be specified for only one funcion in a set of overloaded functions
// error: two extern "C" functions with the same name 

extern "C" void print(const char*); 
extern "C" void print(int);

如果一组overloading function 有一个是 C 函数, 其他都是 C++ function. The C version of calc can be called from C programs and from C++ programs. The additional functions are C++ functions with class parameters that can be called only from C++ programs. The order of the declarations is not significant.

class SmallInt{/* ... */}; 
class BigNum{/* ... */};
// the C function can be called from C and C++ programs

// the C++ functions overload that function and are callable from C++

extern "C" double calc(double);
extern SmallInt calc(const SmallInt&); 
extern BigNum calc(const BigNum&);




Include Guard

InC and C++, an #include guard, sometimes called a macro guard, header guard or file guard, is a particular construct used to avoid the problem of double inclusion when dealing with the include directive.避免double include

C preprocessor 把include 的file 复制its contents into a copy of the source file known as translation unit. The files included in this regard are generally header files() 包括了 functions, classes, structs的declarations. If certain C or C++ language constructs are defined twice, the resulting translation unit is invalid (如果被defined两次, translation unit invalid). #include guards prevent this erroneous construct from arising by the double inclusion mechanism.

The addition of #include guards to a header file is one way to make that file idempotent. Another construct to combat double inclusion is #pragma once, which is non-standard but nearly universally supported among C and C++ compilers.

Double Inclusion

File “grandparent.h”

struct foo {
    int member;
};

File “parent.h”

#include "grandparent.h"

File “child.c”

#include "grandparent.h"

#include "parent.h"

Result


struct foo {
    int member;
};
struct foo {
    int member;
};

Here, the file “child.c” has indirectly included two copies of the text in the header file “grandparent.h”. This causes a <span style=”color:red””> compilation error</span>, since the structure type foo will thus be defined twice. In C++, this would be called a violation of the one definition rule

Use of #include guards

In this section, the addition of #include guards, the C preprocessor preprocesses the header files, including and further preprocessing them recursively. This will result in a correct source file,

File “grandparent.h”

#ifndef GRANDPARENT_H
#define GRANDPARENT_H

struct foo {
    int member;
};

#endif /* GRANDPARENT_H */

File “parent.h”

#include "grandparent.h"

File “child.c”

#include "grandparent.h"

#include "parent.h"

Result

struct foo {
    int member;
};

Here, the first inclusion of “grandparent.h” causes the macro GRANDPARENT_H to be defined. 在parent 之后, when “child.c” includes “grandparent.h” the second time, the #ifndef test returns false, and the preprocessor skips down to the #endif( 当第二次include parent.h, ifndef返回false, preprocessor skips to endif ), thus avoiding the second definition of struct foo. The program compiles correctly.

Pointer

Function Pointer

  1. Unlike normal pointers, a function pointer points to code, not data. Typically function pointer stores the start of executable code
  2. Unlike normal pointers, we do not allocate de-allocate memory using function pointers. 不用allocate de-allocate memory
  3. A function’s name can also be used to get functions’ address. For example, we can use address operator & or without it. void (*fucPtr)() = fun or void (*fucPtr)() = &fun, 用不用&一样的
  4. We can have array of function pointers. 必须function parameter 和 return type 都是一样的
  5. Function pointer can be passed an argument and can also be returned from a function

Pointers to functions

// fcnPtr is a pointer to a function that takes no arguments and returns an integer

int (*fcnPtr)();

上面例子中, fcnPtr is a pointer to a function that has no parameters and returns an integer, it can point to any function that matches this type. 其中() is necessary. as int *fcnPtr()a declaration for a function named fcnPtr that takes no parameters and returns a point to an integer

Const function pointer

int (*const fcnPtr)();

如果put const before int const int (* fcnPtr)();, 表示function being pointed to would return a const int

Function pointers can be initialized with a function (and non-const function pointers can be assigned a function)

int foo()
{
    return 5;
}
 
int goo()
{
    return 6;
}
 
int main()
{
    int (*fcnPtr)() = foo; // fcnPtr points to function foo
    
    fcnPtr = goo; // fcnPtr now points to function goo
 
    return 0;
}

One common mistake is to do this: fcnPtr = goo();. This would actually assign the return value from a call to function goo() to fcnPtr. 是assign funcion 返回的值

Note the type of the function pointer must match the type of the function

// function prototypes

int foo();
double goo();
int hoo(int x);
 
// function pointer assignments

int (*fcnPtr1)() = foo; // okay

int (*fcnPtr2)() = goo; // wrong -- return types don't match!

double (*fcnPtr4)() = goo; // okay

fcnPtr1 = hoo; // wrong -- fcnPtr1 has no parameters, but hoo() does

int (*fcnPtr3)(int) = hoo; // okay

Calling a function using a function pointer

Explicit dereference

int foo(int x)
{
    return x;
}
 
int main()
{
    int (*fcnPtr)(int) = foo; // assign fcnPtr to function foo

    /* The above line is equivalent of following two 
       int (*fcnPtr)(int); 
       fun_ptr = &fun;  
    */
    
    (*fcnPtr)(5); // call function foo(5) through fcnPtr.

    fcnPtr(5); // call function foo(5) through fcnPtr.
    
    int (*fcnPtr2)() = &foo;//assign pointer to function

    (*fcnPtr2)(5); // call function foo(5) through fcnPtr.

    fcnPtr2(5); // call function foo(5) through fcnPtr.

    return 0;
}

Implicit dereference : 就像normal function call, since normal function names are pointers to functions anyway!

int foo(int x)
{
    return x;
}
 
int main()
{
    int (*fcnPtr)(int) = foo; // assign fcnPtr to function foo
    
    fcnPtr(5); // call function foo(5) through fcnPtr.
 
    return 0;
}

需要注意 Default parameters won’t work for functions called through function pointers: 因为default parameters are resolved at compiled time(意味着if you don’t supply an argument for a defaulted parameter, the compiler substitues one when code is compiled), 但是function pointers are resolved at run-time. Consequently, default parameters cannot be resolved when making a function call with a function pointer. 你不得不pass in vlaues for any defaulted parameteres.

Array of function pointers

void add(int a, int b){
    cout << "a + b  = "<<a+b<<endl;
}

void subtract(int a, int b){
    cout << "a - b = "<<a+b<<endl;
}

void multiply(int a, int b){
    cout << "a * b = "<<a*b<<endl;
}


int main()
{
    const int r = 3;
    void (* FucPtrArray[])(int, int) = {add, subtract, multiply};
    FucPtrArray[2](5,3);
    // It's the same as (*FucPtrArray[2])(5,3);

}

Passing function as arguments to other functions

Functions used as arguments to another function are called callback functions 比如让用户选择自己的sorting algorithm 在selection sort algorithm,

bool (*comparisonFcn)(int, int);//因为compare tow interters and return a boolean value

#include <algorithm> // for std::swap, use <utility> instead if C++11

#include <iostream>
 
// Note our user-defined comparison is the third parameter

void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int))
{
    for (int startIndex = 0; startIndex < size; ++startIndex)
    {
        int bestIndex = startIndex;
        for (int currentIndex = startIndex + 1; currentIndex < size; ++currentIndex)
        {
            if (comparisonFcn(array[bestIndex], array[currentIndex])) // COMPARISON DONE HERE

                bestIndex = currentIndex;
        }
 
        // Swap our start element with our smallest/largest element
        
        std::swap(array[startIndex], array[bestIndex]);
    }
}
 
// Here is a comparison function that sorts in ascending order

bool ascending(int x, int y)
{
    return x > y; // swap if the first element is greater than the second
    
}

bool evensFirst(int x, int y)
{
	// if x is even and y is odd, x goes first (no swap needed)
    
	if ((x % 2 == 0) && !(y % 2 == 0))
		return false;
 
	// if x is odd and y is even, y goes first (swap needed)
    
	if (!(x % 2 == 0) && (y % 2 == 0))
		return true;
 
        // otherwise sort in ascending order
        
	return ascending(x, y);
}
 
// Here is a comparison function that sorts in descending order

bool descending(int x, int y)
{
    return x < y; // swap if the second element is greater than the first
    
}
 
 
int main()
{
    int array[9] = { 3, 7, 9, 5, 6, 1, 8, 2, 4 };
 
    // Sort the array in descending order using the descending() function
    
    selectionSort(array, 9, descending);
    printArray(array, 9);
    //9 8 7 6 5 4 3 2 1

 
    // Sort the array in ascending order using the ascending() function
    
    selectionSort(array, 9, ascending);
    printArray(array, 9); 
    //1 2 3 4 5 6 7 8 9
    
    selectionSort(array, 9, evensFirst);
    printArray(array, 9);
    //2 4 6 8 1 3 5 7 9
    
    return 0;
}

Note: If a function parameter is of a function type, it will be converted to a pointer to the function type. It means

void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int))

is equivalently written as

void selectionSort(int *array, int size, bool comparisonFcn(int, int))

This only works for function parameters, not stand-alone function pointers

Providing default functions: 下面例子, as long as user calls selectionSort normally(不是通过function pointer), the comparisonFcn parameter will default to ascending.

// Default the sort to ascending sort

void selectionSort(int *array, int size, bool (*comparisonFcn)(int, int) = ascending);

Making function pointers prettier with typedef or type aliases: the syntax for pointers to functions is ugly. However, typedefs can be used to make pointers to functions look more like regular variables:

typedef bool (*validateFcn)(int, int);

This defines a typedef called “validateFcn” that is a pointer to a function that takes two ints and returns a bool.

Now instead of doing this

bool validate(int x, int y, bool (*fcnPtr)(int, int)); // ugly

You can do this

bool validate(int x, int y, validateFcn pfcn) // clean

Using std::function in C++11: Introduced in C++11, an alternate method of defining and storing function pointers is to use std::function, which is part of the standard library <functional> header. Both the return type and parameters go inside angled brackets, with the parameters inside parenthesis. If there are no parameters, the parentheses can be left empty.

#include <functional>

bool validate(int x, int y, std::function<bool(int, int)> fcn); 
// std::function method that returns a bool and takes two int parameters

#include <functional>
#include <iostream>
 
int foo()
{
    return 5;
}
 
int goo()
{
    return 6;
}
 
int main()
{
    std::function<int()> fcnPtr = foo; // declare function pointer that returns an int and takes no parameters
    
    fcnPtr = goo; // fcnPtr now points to function goo
    
    std::cout << fcnPtr(); // call the function just like normal
 
 
    return 0;
}

Type inference for function pointers(Auto): the auto keyword can also infer the type of a function pointer.The downside is, of course, that all of the details about the function’s parameters types and return type are hidden(parameters types 和 return types 都是hidden), so it’s easier to make a mistake when making a call with the function, or using its return value. Unfortunately, type inference won’t work for function parameters (even if they have default values(Auto 不能传入用于function parameters)

#include <iostream>
 
int foo(int x)
{
	return x;
}
 
int main()
{
	auto fcnPtr = foo;
	std::cout << fcnPtr(5);
 
	return 0;
}

Because the native syntax to declare function pointers is ugly and error prone, we recommend you use typedefs (or in C++11, std::function).