- 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
- badbit: not possible to use when
- 每一个流都管理一个缓冲区(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. 默认是
cin
和cerrr
绑定到cout
, reading tocin
or writing tocerr
flushes the buffer in cout.tie
: 一个版本是接受个pointer,cin.tie(&cout);
tie
: 另一个版本是返回现在绑定的streamostream* 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
andtrunc
implicitly. 只specifyout
,trunc
implicitly. 只specifytrunc
,out
implicitly - when specify
app
onofstream
,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 forconst 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
- capture list capture local nonstatic varaibles 比如
- 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(callpush_back
), front_inserter (callpush_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 iteratorofstream_iterator
的++
是 read next value from input streamofstream_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位置), andcrend
(是在begin开始前的iterator) base
是 next element to thereverse_iteartor
pointing to
- 获取reverse iterator
- 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
阻止 throwbad_alloc
int *p2 = new (nothrow) int;
: 如果不能分配内存, 返回空指针
- 可以用
new
anddelete
: error-pronenew
: allocates, 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
: 知道不指向任何对象
- 删除后的pointer 又被叫做 dangling pointer. dangling pointer has all problem as uninitialized pointer. by set as
- 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
anddelete
, 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
- if we do not initialize a smart pointer, it is initialized as a null pointer
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
- pointer used to create smart pointer 必须point to dynamic memory, 因为 smart pointer use
- 如果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, 其他falsep.use_count
: 返回与p 共享对象的智能指针数量, 可能很慢, intended primarily for debugging purposesmake_shared
: safest way and returns a shared_ptr that poitns to that object- 可以用
auto
savemake_shared
resultauto p6 = make_shared<vector<string>>();
- 可以用
- constructor that takes a smart pointer 是 explicit的
unique_ptr
- 没有像
shared_ptr
的make_shared
function - 不能copy constructed
- 不能copy,可以调用
release()
(切断unique_ptr和它原来管理的对象联系) orreset()
(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
throwbad_array_new_length
- 可以put element in
{}
, 但是不能put in()
, 不能用auto
放在new
后面 to allocate an array 比如new auto
- 如果 initializer element 大于 size,
- 可以用
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;
- the pointer return from
- object allocated on heap is unamed.
- 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
- deleter 需要 deleter type in bracket
- 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
andend
, 因为返回时 pointer. 也不能用 rangefor
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
- 如果不加上
- shared_ptr : 必须provide deleter 但不用specify deleter type, 否则删除就是 array 第一个元素
- 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
- 顺序方法一:
- copy right-hand to local temporary
- destory existing member of left-hand operand
- 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
- can only use
=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, 否则 useswap
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
不用providestd::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++();
postfixp.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 中没有, thenBase *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
- 当compiler 看见
- 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
, ifT
isint*
, return type isint
, 如果T
isint
, return type isint
- 如果not possible to transform,
- 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&p
的const
是 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 referenceforward<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, returnType& &&
is lvalue reference
- if argument is rvalue,
- 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 thanconst T& p
- 看overloading match 例子
- 注意string literal types
"Hi"
is const char array, 所以有const T&a
和const T*a
都available 优先call pointer的, 因为 array to pointer conversion 属于exact match,const T*a
比const T&a
更specialized - rvalue reference 只用于forwarding argument or template overloading
template <typename T> void f(T&&);
binds to nonconst rvalues and lvaluestemplate <typename T> void f(const T&);
const lvalues and rvalues, 因为有const conversion (不是top的),所以上面的不是exact match- 如果同时
T&&
和const T&
对于nonst lvalue, callT&&
因为更specialized 的
- 根据conversions 来排序 (array/function to pointer, lower const conversion), 如果several functions viable
- 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 iskey_type
- 2
result_type
(size_t
) andargument_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, 需要makehash<key_type>
asSales_data
friends key_type
需要有==
定义hash<Sales_data>
definition starts withtemplate<>
, which indicates that we are defining a fully specialized template
- 1 call operator returns
17.Specialized Library Facilities
tuple<T1,T2,...,Tn>t
are value initializedtuple<T1,T2,...,Tn>t(t1,t2,...,tn)
: constructor is explicit (direct initialization 不能conversion constructed)make_tuple<v1,v2,...,vn)
: type inferred from types of initializert1==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, 不可比
- 因为tuple defines
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
- 如果tupleType 不知道, 可以用
-
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 explicitbitset<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 valuev
),reset
(turn off bits),flip
, 是overloaded, 如果没有提供argument, apply to entire sequence~bitvec[0]
; 等于bitvec.flip(0)
b.size()
is constexpr .b.to_ulong()
如果等号左边的range 小于 它, throwoverflow_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 matchregex_search
只要有substring in the input sequence matchesregex_search
andregex_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
callregex_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
- constructor call
- 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, 也相等
- constructor
smatch
operation:ready()
(如果被set 到了regex_search
orregex_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
fromsmtach
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
: 表示whetherssub_match
was matched, 比如optional?
match了first second
: match rangelength()
: Returns 0 if matched is falsestr()
: string containing the matched portions=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 arrayregex_replace(dest, seq, r, fmt, mft)
: dest is output iteratorregex_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 distributionrand()/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,, 因此这种方式值用于生成种子 间隔为秒级 或更长的应用
- 另一方法seed engine when create engine, e.g.
- 即使是random, 每次return the same sequence of number, useful for debugging
- make that generator (both the engine and distribution objects) static
- function-object classes 定义了call operator takes no arguments and returns a random unsigned number
- 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 namedouble
: distribution 用于生成floating-pointint
: distribution 用于生成integral type
normal_distribution<> n(4,1.5);
mean 4, standard deviation 1.5
- engine pass to distribution,
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
- functions along the call chain 可能提早退出 (prematurely exited):
- 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
- parameter 是initialized by exception object
- 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 acatch
- rethrow 不specify an expression
- 如果改变parameter 再rethrow, 只有parameter 是reference时候才会pass up the call chain to another catch
- empty
- 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 callterminate
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)`` 无所谓
- 如果function pointer 有
- 如果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 executinglogic_error
:表示我们可以从程序代码中发现的错误exception
,bad_cast
, andbad_alloc
define default constructor-
runtime_error
andlogic_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
incplusplus
表示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);
不同specifyusing std::string
- search namespace where argument class is defined in addition to normal lookup
- 因为
move
andforward
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
- using declaration:
- 没有限制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
orvirtual 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 typedynamic_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 fromtype_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
- 唯一constructed 方法是 through
- 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
- bc call a pointer to member, must use
- 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
- multiple data members, but only one member have a value. when one member is assigned, all other undefined
- 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,
- value controlled by processes outside direct control of the program
- 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
- reference, pointer:
- 等号两边类型(type)必须相同 , 除了一个特殊情况 const reference
- 比如
double dval = 3.14;,int &refVal5 = dval;//error
- 比如
- 等号两边类型(type)必须相同 , 除了一个特殊情况 const reference
- 对于const非reference 非pointer, 等号左右两边等号类型可以不同.
- 对于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 = π // 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
- 非reference, 非 pointer,Const Initialize时候 等号两边type可以不一样, e.g.
int a = 0; const double b = a;
double a = 0; const int b = a;
- reference or pointer, Const Initialize时候 等号两边type 必须strictly 一样
(1). Reference
const
value 必须initialize when define- 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
.
- non const -> const value e.g.
- 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;
- Const Ref -> Ref Error const reference 不能bind 到non-const reference,因为reference is not object
- 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;
- Ref -> Const Value ✔️,
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
- 如果不是const reference,
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 reference
initialized时, 并没有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
- 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)
- low-level const(object is const): const 在pointer/reference 左面表data is const, cannot change underlying data value. but can point to different underlying.
- 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;
- Pointer of Const -> Pointer: Error e.g.
- 对于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;
- Value -> Const Pointer ✔️ e.g.
- 对于pointer之间的copy, 如果忽略Low Level const, Error
- 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 = π
// 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
- typedef:
typedef double wages
, typedef can also include type modifiers (such as reference, pointer) that define compound types - 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 &
orauto*
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
deducedint&
orconst int&
isint
- 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 namecin
from the namespacestd
using namespace::name
: A using declaration let us use a name from a namespace without qualifying the name with anamespace_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 sameusing
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 ofs1
string s2 = s1
与s2(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 literalstring s4(n,'c')
, initializes4
with n copies of characterc
用=
初始化叫做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
- 如果两个strings 长度不一样 且 每个位置char都一样, 那么较短的string
<
较长的 - 如果有位置不一样, 那么对比那个位置两个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 digitisalpha(c)
: true if c is a letteriscntrl(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 digitisgraph(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 letterisprint(c)
: true if c is a printable character. printable character = space + graph characterispunct(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 emptyvector<T>v2(v1)
: v2 has a copy of each element in v1vector<T>v2 = v1
: v2 is copy of the each element in v1vector<T>v3(n,val)
: v3 has n elements with value valvector<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 initializersvector<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_iterator
type. Within a non const vector , we can use eitheriterator
orconst_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 intint (*Parray)[10];
, 是pointer, 指向 size 为10 的 int arrayint (&arrRef)[10];
, 是reference, refer size 为10 的 int arrayint *(&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 cstddef
header, 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的begin
和 end
返回的是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
, likesize_t
, theptrdiff_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
headerstrlen(p)
: 返回p的长度, not counting the nullstrcmp(p1,p2)
: compares p1 and p2 for equality. Returns 0 if p1 == p2, a positive value if p1 > p2, a negative value if p1 < p2strcat(p1,p1)
: Appends p2 to p1. Returns p1. p1必须large enough to hold resultstrcpy(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 string
const 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}};
orint 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
时候, 当我们applydecltype
时候, 假如p
是int*
, deference yields a lvaluedecltype(*p)
isint&
. Address-of operator yields an rvalue,decltype(&p)
isint**
- prefix operators (
(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++
incrementpbeg
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
andf2
must be called before 乘法. 但是no way of knowing whetherf1
先执行还是f2
先执行- 准侧:
- 当不确定时, 用括号,
- 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)/n
和 m/(-n)
都等于-(m/n)
, (-m)%n
和 m%(-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)
orsizeof expr
sizeof
is a constant expression of typesize_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
, andunsigned short
are promoted toint
if all possible values of that type fit in an int. Otherwise, the value is promoted tounsigned 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
- 当Array 用在
- Pointer Conversion: a pointer to any nonconst type can be converted to
void*
, any a pointer to any type can be converted to aconst 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 classesexception
,runtime_error
,range_error
,overflow_error
,underflow_error
,logical_error
,domain_error
,invalid_argument
,length_error
(试图建立一个超出该类型最大长度的对象),out_of_range
new
header defines thebad_alloc
exception typetype_info
header defines thebad_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(...);
(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(预处理器).
- assert 是 preprocessor 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);
incassert
header 中: evaluatesexpr
and if expression is false, 输出信息并终止程序的执行, 如果表达式正确 什么也不做,- assert 常用来检查不能发生的情况,
assert(word.size() > threshold);
- assert 常用来检查不能发生的情况,
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 defineNDEBUG
.- 也有个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 writingdefine NDEBUG
at the beginning of main.cpp
- 也有个compiler provide a command-line option that lets us define preprocessor variable
- 定义了
NDEBUG
能避免各种条件所需的runtime 开销 (avoid run-time overhead involved in checking various conditions). no run-time check - 如果
NDEBUG
is not defined, the code between theifndef NDBUG
andendif
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
, 而callf(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:
- 精准匹配(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)
- Match through a const conversion (通过const 转化实现)
- Match through a promotion (通过类型提升实现, e.g.
short->int
) - Match through an arithmetic or pointer conversion (通过算数或者指针转换 (e.g. pointer -> bool
if(*cp)
)) - 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
orzero
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), Func
和Func2
是函数类型, FuncP
和FuncP2
是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:
- User code cannot inadvertently corrupt the state of an encapsulated object.用户不会无意间破坏封装对象
- 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 thatthis
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
isconst
pointer to the non const version of the class type(top level const, lower level nonconst).*this
指reference 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 oftotal
to the implicitthis
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 ifthis->bookNo
- It’s illegal to define a parameter or variable named
this
.
const Member Functions
const
is to modify the type of the implicitthis
pointer, 表示this
is a pointer toconst
. Member function that useconst
被称为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 aconst
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 function中call nonconst member function, 只能call const 的function. 因为Cannot bind
- const member function that returns
*this
as a reference should have a return typeconst class &
., 不能返回nonconst reference (因为this
is a const reference, cannot convert to a const reference to nonconst reference ).- 但是如果不是返回
this
, 不必是const reference, 比如可以返回普通reference likeint&
(但若外面接用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, ' ') };
如果move
和 set
返回都是class的reference(*this
),
inline Screen &Screen::set(char c)
{
contents[cursor] = c;
return *this;
}
//则可以用
myScreen.move(4,0).set('#');
如果move
和 set
返回都是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 toScreen
- 还用只声明一个class 的function 有friend 权限, Making a member function a friend 需要仔细设计程序, 比如下面例子中,
clear
是Window_mgr
member 但用到Screen
的 member- 首先定义class
Window_mgr
. 其中声明但不定义clear
functinon, - 再定义class
Screen
, including a friend declaration forclear
- 最后define
clear
此时才可以使用Screen
的成员
- 首先定义class
- 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
- 需要provide
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
- Look for declaration of the name in the block (before use of name) which the name was used. 只在名字出现在块中(使用之前的)查找
- 如果名字没有发现, look in the enclosing scope(继续查找外层作用域)
- if no declaration found, program is error
class defintion are processed in order of :
- memeber declarations are compiled (先编译成员声明)
- 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
balance
的Money
来自于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, 不建议使用成员的名字用作函数参数)
- 首先在member function内部寻找(在name 被使用之前部分)
- 如果没有找到, 再到class 内部继续寻找declaration. All the members of the class are considered (class所有成员都被考虑)
- 如果没有找到, 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. 因为verify
在setHeight
前定义,因此找到
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) {}
)
- 如果there is an in-class initializer, 用default constructor 用初始值初始化成员 比如class 中
- default constructor 是constructor work without any argument(either no parameters, or all parameters have default values); 错误的概念是constructor with no parameters; 比如
- 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 list 让 j
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:
- when we define nonstatic varaibles or array without initializers
- When a class that itself has members of class type uses the synthesized default constructor
- 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 whereT
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 istream
constructor 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:
- Requires that all data members of class be public
- 将正确初始化的重任(burden)给了user. error-prone, 因为用户容易忘记值, 或者提供一个不正确的初始值
- 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)
- scalar type 包括了
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) andconstexpr 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 beconst
, 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 ofthis
and implicit use ofthis
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 defineperiod
outside classAccount
, 但是程序细微的改变不能造成无法编译, 因为找不到该成员定义. e.g. passperiod
to a function that takes aconst 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, 所以比如 fstream
有ifstream
, 也有wifstream
.
- ifstream and istringstream inherit from istream, 因次可以像使用istream对象一样使用
ifstream
和istringstream
, 同样, 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 定义了四个constexpr
values of typeiostate
- 一旦
badbit
set, 不能再使用stream了, failbit
set after recoverable error, 比如读了个char when numeric expected. Possible to correct problems and continue using the streamgoodbit
guaranteed to have value 0, 表示no failures on stream.- 如果任何
badbit
,failbit
, oreofbit
set, then condition that evaluates that stream fails. s.good()
ors.fail()
(fail or bad) 是确定stream 总体状态的正确方法. 实际上, 把流当条件使用的代码等于!fail()
,s.eof
和s.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
andcerr
are both tied tocout
. 因此reading tocin
or writing tocerr
flushes the buffer incout
.
Manipulators
endl
: end current line and flush the bufferflush
flush the stream and adds no character to the outputends
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
tocin
. 因此cin >> ival
会导致cout
的缓冲区刷新(flush)
- The library ties
- 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 streamx
to the output streamo
. - can tie either an
istream
or anostream
object to anotherostream
下面代码中, 将一个给定的流关联到一个新的输出流, 我们将新流的指针传递给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()
再openin.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
orfstream
object,out
is default forofstream
in
只能对ifstream
orfstream
object,in
is default forifstream
- 只有当
out
is specified, 才能设定trunc
模式 - 只要当
trunc
没被设定, 就可以设定app
模式. 如果指定app
, 即使没有explicitly specifyout
, 文件也是以输出方式打开 - By default,
trunc
is default mode forout
(如果不设定, 默认是trunc
).如果要保留文件内容, 需要specifyapp
, or specifyin
mode which file is open for both input and output - The
ate
andbinary
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 frormiostream
header. 除了继承,sstream
中定义的类型还增加了一些成员管理string
, 下面表中可以对stringstream
对象调用这些操作, 但不能对其他的IO类型进行操作- 即使
sstream
andfstream
share the the interface asiostream
. They have no other interrelationship. We cannot useopen
andclose
on astringstream
. 也不能usestr
on anfstream
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 assignment 和 copy 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 ofswap
Syntax | Description |
---|---|
c1 = c2 |
Replace element in c1 with copies of elements in c2 , c1 与c2 类型必须一样 |
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 tolist<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
orstring
添加元素可能会引起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 因为会改变sizeemplace(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
与下标的区别是如果越界,at
throw 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
- vector 和 string 通常会分配比新的空间需求更大的内存空间,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 holdsreserve
允许我们通知container how many elements it should prepare to hold.reserve
不改变容器中元素的数量,仅影响预先分配多大的内存空间 (how much memory thevector
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, 可以callshrink_to_fit
Callingshrink_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
- 如果request space > current 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
orstring
的end
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, 但可以callforward_list
的end()
- 不支持
push_back
- 有自己的
insert
andemplace
- 没有
因为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 namedstring::npos
. Library definesnpos
as aconst 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. 如果转换得到数值无法用任何类型来表示, throwout_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
, andpriority_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.
- 默认情况下,
stack
andqueue
are implemented in terms ofdeque
, 而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)
- e.g. 假定
- 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
,所以用于除了array
和forward_list
以外所有容器 (list. deque,vector,string)queue
需要有back
,push_back
,front
,push_front
的能力,所以只能建立在list
ordeque
上而不能在vector
上.priority_queue
需要有random access in addition tofront
,pusk_back
,pop_back
, 所以只能用于vector
ordeque
, 不能用于list
- 每一个container adpator defines 自己的操作类型 in terms of 底层容器的操作类型. 只能使用adaptor自己的操作类型,不能用underlying container type的操作类型.
intStack.push(ix);
callspush_back
on thedeque
on whichintStack
is based,尽管stack
是基于deque
实现的, 但我们不能使用deque
操作, 不能callpush_back
, 只能callpush
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()
andcend()
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
- back_inserter: takes a reference to a container and returns an insert iterator bound to that container. 当assign through that iterator, assignment calls
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
)
- The capture list is used for local nonstatic variables only; lambdas can use local statics and variables declared outside the function directly(比如
- 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
不能更改
- lambda that are mutable不能省略parameter list
- If we want change the value of a captured variable, we must follow the parameter list with the keyword
- 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(显示捕获和隐式捕获必须是不同的方式): 如果隐式捕获是引用(
&
), 则显示捕获必须是值, 不能在显示名字前加上&
; 如果隐式捕获是值方式, 则显示捕获必须用引用方式 (加上&
)
- 必须确保the first item in capture list is
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
(infunctional
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 tobind
newCallable
is itself a callable objectarg_list
is 逗号分隔的参数列表,对应给callable
的parameter- 也许include names of the form
_n
, n是整数, 这些参数是”placehorders”(infunctional
header),表示newCallable
parameters. They stand “in place of” the arguments that will be passed tonewCallable
:_1
为newCallable
第一个参数,_2
为newCallable
第二个参数 - The _n names are defined in a namespace named
placeholders
(instd
), 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
- 也许include names of the form
- 当调用
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++提供了
bind1st
和bind2nd
有更多限制,已经被deprecated 在新版中
- Modern C++ programs should use bind. 旧版C++提供了
下面的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个参数, g
is 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
- Insert iterator: 这些迭代器被绑定到一个容器上, 可以像容器插入元素
- Stream iterator: 这些迭代器被绑定到输入或者输出流上, 可以用来iterate through the associated IO stream
- Reverse iterator: 这些迭代器向后(backward)而不是向前移动. Container 除了
forward_list
都有reverse iterators - 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 , 假定c 是it 绑定的容器, 依赖于插入迭代器的不同种类, 此赋值会分别调用 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 byinserter
, 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_iterator
constructor 生成的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,那么何时读取就很重要了
- 对于大多程序, 何时读取没有差别, 但是如果我们创建一个
- the type that an
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 |
in1 和 in2 必须读取相同类型. 如果他们都是尾后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
, andcrend
: to last element and one “past” the beginning of the container - 下面例子只中
[line.crbegin(), rcomma)
和[rcomma.base(), line.cend())
指向相同的元素范围, 所以rcomma
和rcomma.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.
- Input iterator: Read, but not write; single pass, increment only
- Output iterator: Write, but not read; single pass, increment only
- Forward iterator: Read and write, multi-pass, increment only
- Birectional iterator: Read and write, multi-pass, increment and decrement
- 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 anostream_iterator
. Insert iterator 是add new elements to container 确保enough space. Anostream_iterator
writes to an output stream, 也是不管写多少个元素都没有问题
alg(beg, end, beg2, other args);
- 将
beg2
作为第二个输入范围的首元素, 此范围结束位置未指定. 算法假定从beg2 开始范围 与beg
和end
所表示范围至少一样大
- 将
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
list
和forward_list
定义了他们独有的sort
,merge
,remove
,reverse
, andunique
, 因为generic version ofsort
requires random-access iterators, 因此sort
不能用于list
和forward_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
andsplice
are destructive on their arguments(会销毁参数).例如generic merge将merged sequence to a given destination iterator; the two input sequences are unchanged. 而list
的merge
destroys (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 whichfirst
holds the const key andsecond
holds the value. 可以改变value 值 但不能改变key的值
- 对于map,
- Iterators for
sets
Are const,虽然sets定义了iterator
和const_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
, 用它比genericfind
algorithm 快的多(generic algorithm use sequential search) - 如果把associative container with algorithms either source sequence or as a destination.可以使用
copy
orinserter
to bind an insert iterator (用inserter
,可把associative container as a destination for another algorithm)
- 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
- 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, 会throwout_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 avalue_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
越小, 更频繁callrehash
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 callsrehash(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
- 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
- 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:
new
和delete
error-prone, cannot rely on the default definitions for the members that copy, assign, and destroy class objectsnew
: 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
- Using new to Dynamically Allocate and Initialize Objects
- Dynamically Allocated const Objects
- 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 bynew
is a pointer toconst
// 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 ofnew
- 下面例子阻止抛出异常叫: placement new, 传递一个由标准库定义的
nothrow
对象, 如果将nothrow
传递给new
, 我们是告诉它不要抛出异常, 如果new
不能分配内存, 返回一个空指针. bad_alloc
和nothrow
都定义在new
header中
- 可以prevent
阻止抛出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.
- 忘记
delete
内存 – 内存泄漏问题, memory is never returned to the free store, 查找内存泄漏错误是困难的, 因为通常程序运行很长时间后,真正耗尽内存是,才能检测到这种错误 - Using an object after it has been deleted. This error can sometimes be detected by making the pointer null after the delete.
- 同一块内存释放两次. 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_ptr
和 unique_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_shared
的shared_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.
- 当use shared_ptr to initialize another
- 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
放进一个容器中, 如果不需要使用, callerase
删除不再需要的那些元素.
下面例子中当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:
- They don’t know how many objects they’ll need 不知道自己需要使用多少对象
- They don’t know the precise type of the objects they need, 不知道所需对象准确类型
- 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 thannew
.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 toprocess
is copied intoptr
. 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.
下面第一个例子 即使有异常, 内存会自动被释放, 第二个例子, 如果异常在new
和 delete
之间且异常未被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
- 不使用相同的built-in pointer value to initialize more than one smart pointer
- 不delete
get
返回的指针 - Don’t use
get()
to initialize orreset
another smart pointer.(不把get返回的指针跟其他smart pointer 绑定) - If you use a pointer returned by
get()
, remember that the pointer will become invalid when the last corresponding smart pointer goes away. - 如果使用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 theunique_ptr
is destroyed - Unlike
shared_ptr,
there is no library function comparable tomake_shared
that returns aunique_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和它原来管理的对象联系) orreset
- release 返回的通常用来initialize or assign 另一个smart pointer
- 虽然不能copy, 但可以调用
- 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
anddelete
operators allocate objects one at time 一次只能分配释放一个- 例如vector 和string, 有时候需要一次为很多对象分配内存. C++ 定义了两种 an array of objects at once(一次分配一个对象数组)
new
:allocator
let us separate allocation from initialzation. Useallocator
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 rangefor
来process the elements in a (so-called) dynamic array
- 不能call
- 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 typebad_array_new_length
(innew
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
- 新标准下, 可以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,
- 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 bynew
. 就像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
- When we use
- 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 destroys the pointer it manages, it will automatically use
- 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
时忘记加[]
是一样的问题
- 如果没有提供 deleter, the code would be undefined: 跟我们使用动态数组,
- 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.
- We must
- When we’re finished using the objects, we must
destroy
the elements we constructed, which we do by callingdestroy
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 byallocate
(分配内存的起点). 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少了, 不会报错,会内存泄漏
- pass给
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_copy
和uninitialized_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
TextQuery
和QueryResult
- 用
shared_ptr
用来传递set
(这个string 在第几行出现) 和vector<string>
(整个file 文本) 避免拷贝. query
是const
function:cons
t function 巧妙使用, 因为不会改变class obejctauto &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,避免多次定义一样的nodatafor (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 不仅在我们用
=
定义变量时发生, 在下面情况也发生:insert
和push
是copy initialization, whereasemplace
是 direct 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
- 下面例子很重要,
- Copy initialization 不仅在我们用
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).
- We can use
- Preventing Copies 对于不能copy 的class i.e IO class: 以免多个对象写入或者读取相同的IO缓冲
- 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(因为无法删除).
- Unlike
- 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
- 当 copy constructor and copy-assignment operator are private, user 不能 copy such objects. However, friends and members of the class can still make copies
- Defining a Function as
如果只定义了destructor, 而没有定义copy constructor 和 copy-assignmnet operator 是错误的. 比如下面例子, 使用了synthesized versions of the copy constructor and copy-assignment operator,造成错误是多个HasPtr
指向相同的内存, 比如ret
和 hp
, 两个对象包含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. 顺序很重要
- 像destructor, 先destroys the left-hand operand’s resouces
- 像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:
- copy right-hand operand into a local temporary.
- step1 完成后, safe to destory existing member of left-hand operand.
- 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 a
和HasPtr 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:
- 每一个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
- copy constructor not allocate a new counter; copies the data members of its given object, including the counter 并 递增reference counter
- destructor 递减counter, 如果count 变成0, destructor deletes that state
- 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
anduse
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, useswap
defined by the library.
- If a class 定义了自己
- 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, callingstd::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
.
- 下面例子有正确的使用方法: 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
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 theHasPtr
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’sconstruct
member to create objects. 当remove 时候用 allocator 的destory
- 每一个StrVec 有三个pointer
elements
, points to first element in the allocated memoryfirst_free
, points just after the last actual elementcap
, points just past the end of the allocated memory
- 还有个member named
allocator<string> alloc
.alloc
will allocate the memory used by aStrVec
. - 有四个ultility functions:
alloc_n_copy
will allocate space and copy a given range of elements.free
will destroy the constructed elements and deallocate the space.chk_n_alloc
will ensure that there is room to add at least one more element to StrVec,如果没有足够空间, callreallocate
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 elementallocate
返回 start of memory block- Moving, Not Copying, Elements during Reallocation. function 应该做:
- Allocate memory for a new, larger array of strings
- Construct the first part of that space to hold the existing elements by move
- 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 ausing
declaration (将在18.2.3中讲到), 使用move 直接call的是std::move
notmove
- 下面code中的
reallocate
在move
完成之后, 不知道旧的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
- r-value reference <– rvalue✔️:
- 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✔️
- lvalue <– rvalue Error
例子
//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
- Move Constructor
- Move Assignment
- Synthesized Move Operations
- Move Iterators
- Rvalue References and Member Functions
- 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 header 和 definition outside class - Move constructors and move assignment operators that cannot throw exceptions should be marked as noexcept.
- specify
- move operation 不throw exception 基于两个事实:
- 虽然move operations 不抛出异常,但抛出异常是被允许的
- 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 asempty
orsize
on moved-from objects. 但不知道会得什么结果.
- valid 表示可以依然为其赋予新值, 可以安全使用不依赖其结果,比如
例子中 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
- 一个版本是 take an lvalue reference to
- 一般来说,不用为function 定义
const X&&
or a plainX&
. 通常we pass an rvalue reference when want to steal from argument. the argument must not beconst
. 类似的, 拷贝对象不应该改变被copied的对象, no need to define a version that take a plainX&
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 declaration 和 definition 中 - 一个function 可以同时有const 和 reference qualifier. 但reference qualifier 必须跟在
const
后面
- 与
- 就像
const
function 可以被overload, overload a function based on its reference qualifer. 我们可以综合 reference qualifier 和 const 来区分一个成员函数的重载版本- 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
- assignment
=
, subscript[]
, call()
, and member access arrow->
` must defined as members - compound-assignement operators 比如
+=
应该是 members - 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.
- 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 可以在 加号左侧 右侧都可以,
比如 加号(string
和 const 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
- return its
- 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 state
failbit
,eofbit
(file was exhausted),badbit
(stream corrupted).
- 如果错误发生错误, 应当set IO state
下面例子, 如果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==c
则a==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:
- Define an ordering relation that is consistent with requirement for as a key to an associative container
- 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 elementsvector<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 typeint
. 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类型不同, writingpoint->mem
resolve 步骤是:- 如果
point
是pointer type ,则用(*point).mem;
. - 如果
point
是class type with overloaded operator->
则用point.operator->()mem;
来fetchmem
- 如果operator
->
function return pointer type, goto step 1 - 如果返回的
res
是class, 继续step2,point.operator->()mem
转化为res.operator->()mem;
直到fetch的是pointer type or some error
- 如果operator
- 如果
比如下面例子, 最好获得是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 aStrBlobPtr
. 这些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. 比如可以defineset
of pointers or use a pointer as key inmap
without specifyless
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 infunctional
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
- call signature inside angle brackets: 比如
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_type 和second_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
, mod
和 div
都是接受两个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
ordo
statement, condition expression in afor
statement, - An operand to the logical NOT (
!
), OR (||
), or AND (&&
) operators or 条件逻辑表达式 (?:
) operator
- The condition of an
- 这个规定存在一个例外,如果表达式被用作条件, an explicit conversion will be used implicitly to convert an expression used as
比如下面例子既定义了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 fromBar
, 不要给Bar
conversion operator toFoo
- 比如
- 第二种是: 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
toFoo
- Do not define conversion operators to more than one arithmetic type. Let the standard conversions provide conversions to the other arithmetic types. 比如定义了
Foo
toint
, 不要定义Foo
todouble
- Do not define overloaded versions of conversion constructor that take arithmetic types. 比如 定义了 int to
- 如果定义了多种方式conversions, 之所以ambiguity, 因为conversions have the same rank(the rank of standard conversion)
- 如果有ambiguity, 不能用cast 解决问题
- 第一种provide mutual conversion (converting constructor and conversion operator)
- 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
toFoo
还是double
toBar
), 是ambiguity, rank of any standard conversion 不会被考虑. 只有当同一个 type user-defined conversion时候(比如是short
toFoo
还是double
toFoo
时候), rank of standard conversion 才 被考虑
- Call to an overloaded function, if two (or more) 转化为不同type的 都可行的 (比如 10,
下面例子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 byA(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
orBulk_quote
interchangeably. item is referenceconst Quote &
, 可以是Quote
object orBulk_quote
object. 因为net_price
是virtual function, ther version ofnet_price
that is run will depend on the type of the object that we pass toprint_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.
- 如果static 是 private, 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.
- 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.
- 没有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
- 如果当我们知道conversion is safe from base to derived, 可以用
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 Quote
的 bookNo
和 price
. 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 markedoverride
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
final
和override
都 specify parameter list, const, reference qualifier 之后
- return type 一般也要match, 一个例外是 return a reference / pointer to types that are themselves related by inheritance. 比如
- 可以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
- 可以provide a definition for pure virtual. 但是function body 必须被定义outside the class.不能在provide function body inside the class that is
- 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, Pal
is 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
- determine the static type of
p
, 因为我们call member, type 必须是class type - Look for
mem
in class that correponds to static type ofp
. 如果mem
没有找到, look in direct class, chain of classes 直到mem
被发现. 如果mem
没有被发现, call won’t compile - 一旦
mem
被找到, do the normal type checking to see if call is legal given the definition that was found - 假设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
- 如果
- 注意: 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 isbn
在 Disc_quote
(Bulk_quote
derived from Disc_quote
), not found
3. search isbn
在 Quote
(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
却是看不见的, itemP
是 Quote
的指针, 意味着对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
D1
的 fcn
并没有覆盖 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 没有overridefcn
function that takes no argument. 所以call Base 的version - 接下来三条 calls are through pointers with differing types.
bp2->f2();
is illegal 因为没有 static type ofbp2
isBase
, nof2()
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的, 注意顺序,
- 声明Derived, 因为Base 中的friend 用到derived类
- 定义Base 类, 因为只有Base定义了, 才能作为Base Class 用于Derived class
- Derived Class 再 friend function 前, 因为friend function 用到了 derived function 的member
- 因为
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
- These compiler-generated constructors have the form
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, 让 vector
存Quote
, 但是当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_bound
在multiset
中用的很巧妙, 跳过相同的所有元素print_total
调用了 virtual call tonet_price
, resulting price 取决于dynamic type of**iter
- Users of Basket still have to deal with dynamic memory,因为
add_item
takes ashared_ptr
- 可以redefine
add_item
to take aQuote
object instead ofshared_ptr
.
- 可以redefine
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 的
sale
是rvalue reference, 但sale
is lvalue. 所以可以callmove
to bind an rvalue reference tosale
- 需要注意的是 rvalue verson 的
clone
function is vritual. WhetherQuote
orBulk_quote
function is run, 取决于 dynamic type ofsale
. 无论是copy or move,clone
返回a pointer to newly allocated object. 因为shared_ptr
support derived-to-base conversion, we can bind ashared_ptr<Quote>
to aBulk_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_ptr
和IO
types 都可以copies. 不allow copy, make is faster for large objects - 注: passing in 的 type只需要定义
<
operator 既可以, 无需提供>
, 如果concerned type independence and portability, 应该定义function usingless
, 原来版本缺陷是, 如果定义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. 实际typeT
determined at compile time based on howcompare
is used.
- 当 function template, compiler uses the arguments of the call to deduce template arguments. 比如下面例子argument 是
int
. The compiler deduceint
as template argument and bind that to template parameterT
.- 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
orconstexpr
defined same as nontemplate function.inline
和constexpr
需要在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 报告错误
- 第一阶段是 when compile the template itself. Compiler 不会find many errors at this stage. compiler 可能detect syntax errors 比如忘记semicolon or 错误拼写variable name
- 第二个阶段是 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
- 第三阶段是 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.
- 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.
- 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, 所以没关系)
- 这使得也许某一type 不 meet requirement for some template’s operations. 我们也可以instantiate a class with that type (比如, template 有的function 需要这个type, 有
- 一个例外是: 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, 也可以加Ttemplate<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 返回 BlobPtr
和 BlobPtr&
, 而不是 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;
- 一对一关系: 用class 实例化template class. ,Template friend class 需要 forward declaration,
- template class -> class friend
- 多对一关系: friend class 类不用forward declaration, 比如
template <typename T> class C2;
中声明friend class Pal3;
- 多对一关系: friend class 类不用forward declaration, 比如
- 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;
- 一对一关系,每个实例 将相同实例化 声明为friend: Template friend 需要 forward declaration. e.g.
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 BlobPtr
和 Blob
equlity operator
下面例子,
- 需要先 声明
BlobPtr
和Blob
, 这些是 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 ofBlob<char>
, 但是Blob<char>
对Blob<int>
没有special access 或者 其他instantiation ofBlob
// 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 list
, separatectr
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
, 错误的
- 不可以直接通过 class template 名字access static member without template argument, 比如
比如下面例子 对一个类型X
, 比如Foo<X>::ctr
and Foo<X>::count
, 共享同样的ctr
和 count
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 namedp
还是将 名为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, notclass
.
- 必须显式告诉 compiler the name is type, by using
- 比如当我们用scope operator
- 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 应该instantiateBlob
version 是 int, constructor’s own paramerter 被deduced from type ofbegin(ia)
和end(ia)
是int*
.Blob<int>::Blob(int*, int*);
a2
是instantiatedBlob<int>
class and instantiates constructor withIt
replaced byvector<short>::iterator
a3
的定义实例化了一个 string 的Blob
,constructor template parameterIt
bound tolist<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 isstd::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 tolibstdc++
.)
- 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 instantiateds->g()
才会实例化X<float>
andX<float>::g()
- compiler 不需要下面defintion 的实例化
- class X 当declare
p
X<int>
when pointer q declaredX<float>
when pointer s declared
- class X 当declare
- 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. 相反deleter
是unique_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 atshared_ptr
lifetime. 可以假设shared_ptr
stores the pointer it manages in a member namedp
. deleter 通过del
去accessdel ? 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 indel
to execute the code to whichdel
points (需要一次运行时的跳转操作,转到del中保存的地址来执行)
unique_ptr
unique_ptr
template parameter 有两个,一个是type thatunique_ptr
manages 里一个是 表示 deleter 的type. 因为type of deleter 是 part of type orunique_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
- Conversions
- Function-Template Explicit Arguments
- Trailing Return Types and Type Transformation
- Function Pointers and Argument Deduction
- References Deduction
- Reference Collapsing and Rvalue Reference Parameters
- Understanding std::move
- 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 被忽略). callfref
,s1
fromstring
toconst string&
是合法的. - 在数组例子中, 因为size 不一样, 类型不同, 在call
fobj
, 数组大小无关紧要, 因为会convert to pointer. 但是对于fref
是非法的, 因为parameter 是 reference, array 不会convert to pointer,a
和b
类型不一样
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 thattype
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 asT
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, 比如p
是int * const int
, 则T
是int * 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
,T
是const char*
, char to const object (lower-level), - 再比如
const int&p
,T
是int
, const 是 lower-level 的
- normal binding say we can pass any kind of argument- an object, temporary, literal value. paramter 的
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& &&
, andX&& &
all collapse to typeX&
- The type
X&& &&
collapses toX&&
- 注意: 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 typeT
是 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 varaiblet
is initialized by copy value ofval
. 当we assign tot
, parameter isval
remains unchanged - 当call
f3
on lvaluei
, thenT
isint&
当define and initialize local variablet
, variable has typeint&
. This initialization oft
binds(reference)t
toval
. when assign tot
, changeval
at the same time. In this instantiation off3
,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 tomove
remove_reference<T>
如果T
是X&&
orX&
,remove_reference<T>::type
isX
, thenremove_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 functionstring&& move(string &&t)
- The deduced type of
T
isstring
. - Therefore,
remove_reference
is instantiated withstring
. - The type member of
remove_reference<string>
isstring
. - 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
- The deduced type of
std::move(s1)
- The deduced type of T is
string&
(reference to string, not plain string). - Therefore,
remove_reference
is instantiated withstring&
. - The type member of
remove_reference<string&>
isstring
, - The return type of move is still
string&&
. - move’s function parameter, t, instantiates as
string& &&
, which collapses tostring&
. - call instantiates
move<string&>
which isstring&& move(string &t)
就是我们想要的, want bind rvalue reference to an lvalue.- the type of t is
string&
which cast converts tostring&&
- the type of t is
- The deduced type of T is
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
(inutility
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 forstd::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 forT1
isint&
, collapses toint&
. The referencet1
bound toj
. 改变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 andforward<Type>
returnType&&
rvalue reference - 如果argument 是 lvalue, 通过 reference collapsing,
Type
is lvalue reference type(Type&
),forward<Type>
returnType& &&
isType&
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 withT
bound toconst string*
(lower-level const),debug_rep
的template parameter 的 const 是 top-level constdebug_rep(const string*)
, the instantiation of the second version of the template withT
bound toconst 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&)
, templateT
bound tostring
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&)
withT
bound tochar[10]
debug_rep(T*)
withT
bound toconst char
debug_rep(const string&)
which requires a conversion fromconst char*
tostring
, 因为需要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
- Writing a Variadic Function Template
- Pack Expansion
- 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...
ortypename...
表示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
就只有一位数, 这时callprint
不会call variadic version - key part of
print
是return print(os, rest...); //
the first argument in packrest
会被 removed from the pack and becomes the argument bound tot
. The remaining arguments inrest
for the next call toprint
的 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 patternconst Args&
to 每一个elements in template parameter packArgs
. - 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&
.
- The expansion of
- 第二个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_back
是 variadic 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 whenemplace_back
passes those arguments toconstruct
(another function)
- 例子中
std::forward<Args>(args)...
expands both template parameter packArgs
and function parameter packargs
.- Pattern generate form
std::forward<Ti>(ti)
:Ti
: ith element in tempalte parameter pack.ti
: ith element in function parameter pack
- Pattern generate form
- By using
forward
in this call, we guarantee ifemplace_back
is called with an rvalue, then construct will also get an rvalue - 比如
svec.emplace_back(s1 + s2);
. Putting in emplace back 是 rvalue, result fromforward<string>
isstring&&
, 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
- Function Overloading versus Template Specializations
- Class Template Specializations
- Class-Template Partial Specializations
- 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&
, 要specializeconst char *
类, 那么特例化的function parameter 是const char * const &
(在下面例子中)
- 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)
比如我们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 viablecompare("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 ofhash
template. 一个specializaedhash
必须定义- 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
andargument_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 )
- An overloaded call operator that returns a
- 必须 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 withtemplate<>
, which indicates that we are defining a fully specialized template. template we specialize is namedhash
and specialized version ishash<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.
- 这里 我们将hash function 交给了标准库, 标准库 defines specializations of the
- 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
- 假设specialization is in scope, it will be used automatically when we use
- 因为
hash<Sales_data>
使用Sales_data
private members (bookNo
,revenue
), 必须makehash<Sales_data>
as friend ofSales_data
. 因为hash<Sales_data>
instantiation 定义在 std namespace, our friend declarations 使用std::hash
- To enable users of
Sales_data
to use the specialization ofhash
, we should define this specialization(hash) in theSales_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()
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 yieldsT&
;
c) if the value category of expression is prvalue, then decltype yieldsT
.
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
- Defining and Initializing tuples
- 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 T1 … Tn . 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 value 是 size_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 argumentget
returns a reference to specified member- inside bracket
<>
must be integral constant expression
- To use
tuple_size
ortuple_element
, need to know type oftuple
object.如果有tuple type 不知道, 可以用decltype
.tuple_size
has public static data membervalue
: 是members的数量 in specifiedtuple
.tuple_element
take index 和 tuple type. has public type membertype
: 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
- 必须有 same number of members. to compare, 必须legal to use
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 tocompareIsbn
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 default 是 string::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 setb.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()();
andb.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.
- Throw an
- 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 orwchar_t
data and those character 被存储到string
or array orchar
(或则wide character versions,wstring
or array ofwchar_t
)regex
hold regular expression of typechar
. library 还定义了wregex
class that hold typewchar_t
and has same operations asregex
. 唯一不同是 initializers ofwregex
必须用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 |
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
objectresult
, 如果a match is found,results
will hold details about where match occurred regex_search
functions stops 当只要找到一个匹配字串,results
memberstr()
, print the part oftest_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
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_error
有what
operation describe the error that occurred regex_error
也有 valuecode
returns a numeric code 表示 type of error that was encountered
- if make mistake writing regular expression, run time library will throw an exceptionf of
- 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
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 stringsregex_iterator
constructor callsregex_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 callsregex_search
to 定位it
on first match infile
. - increment
it
for advances iterator by callingregex_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.
- regex 中必须用 双backslash
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
, thenresults.str(0)
holdfoo.cpp
.results.str(1)
will befoo
;results.str(2)
will becpp
// 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
(\\()?
(\()? an optional open parenthesis for the area code(\\d{3})
the area code(\\))?
an optional close parenthesis for the area code([-. ])?
an optional separator after the area code(\\d{3})
the next three digits of the number([-. ])?
another optional separator(\\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
orregex_match
functions orformat
members of classsmatch
- These flags can be passed to
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;
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
- Random-Number Engines and Distribution
- Seeding a Generator
- 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 byrand
- 一些程序需要different range from produced by
- 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.
- An engine generates a sequence of
- C++ programs 不应该使用
rand
function, 而是使用default_random_engine
along with an appropriate distribution - output of calling a
default_random_engine
similar to output ofrand
. Engines 生成unsinged intergers in system-defined range. rand 生成范围是 0 到RAND_MAX
- The range of an engine type is returned calling
e.min()
ande.max()
members on an object of that type
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
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
time
function (defined inctime
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.
- Using
- 因为```time`` 返回time 以秒计算, 因此这种方式值用于生成种子 间隔为秒级 或更长的应用
比如, print equval, 因为v1
和 v2
具有一样的值, 所以编写此函数的好方法 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 : e1
和 e2
有不同的 seeds 生成different sequences. e3
和 e4
有一样的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
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
isrand()/RAND_MAX
(RAND_MAX
: system-defined upper limit that is the largest random number that randcan
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
- 每一个 distribution template 都有一个default template argument. Distribution 用于 generate floating-point values 默认是 double. Distribution 用于生成 integral type 默认值是 int. 当用default: 在template’s name后提供一个 empty angle bracket
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 parameter 是
bernulli_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
- Formatted Input and Output
- Unformatted Input/Output Operations
- 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
- 大多数manipulator 改变format state 所以提供了
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 theiomanip
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
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 |
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 notationfixed
: change the stream to use fixed decimalhexfloat
: force floating-point values to usedefaultfloat
: returns the stream to its default state: 根据打印的值选择计数法- 上面这些manipulator 也改变了 meaning of precision.
- After executing
scientific
,fixed
, orhexfloat
, 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(默认是小数点前和小数点后 数字总位数).
- After executing
注意 下面例子默认情况下, 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 valueleft
to left-justify the outputright
: right-justify the output. Output is right-justified by defaultinternal
: 控制负数符号位置,它左对齐符号,右对齐值, 用空格填满(padding)中间所有空间setfill
: 允许指定字符代替默认空格来补白输出(pad the outut)- 注意
setw
与endl
类似,不改变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
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
orunget
successively without an intervening read operation.
int Return Values from Input Operations
get
和peek
都返回int,而不是char. 因为int allow them to return an end-of- file marker. char 没有表示end-of-file 的- 返回int 的函数 将他们要
char
convert 到 unsignedchar
再promote toint
. 因此character set 有character map 到负数, theint
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 bysink
. 读取过程直至遇到字符delim
或读取size
个字节 或者遇到文件尾时停止, 如果遇到了delim
则将其保留在input stream and not readdelim
intosink
- 从
is.getline(sink,size,delim)
:- 与上面版本类似,但会读取并丢弃
delim
- 与上面版本类似,但会读取并丢弃
is.read(sink,size)
:- Reads up to
size
bytes into character arraysink
. Returnsis
- Reads up to
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
, orputback
are calling beforegcount
, return value will be 0 </span>
- Returns number of bytes read from the stream
os.write(source, size)
- Writes
size
bytes from the character arraysource
toos
. Returnsos
- Writes
is.ignore(size, delim)
- 读取并忽略最多
size
个字符, 包括delim
. 不像其他未格式化的函数,ignore
has default arguments:size
defaults to 1 anddelim
to end-of-file.
- 读取并忽略最多
get
和getline
functions 接受相同的参数, actions are similar but not identical.- 相同的是
sink
都是一个char array 用来保存数据. delimiter 都不会存到sink
- 不同是 treat delimiter differently.
get
leaves delimiter as next character inistream
.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 andtell
current location in associated stream - 尽管
seek
andtell
are defined for all stream types, 但是比如绑定到cin
,cout
,cerr
,clog
流 不支持random accesss 是没有意义的, , 比如count
直接输出, 向回跳10个位置没有意义- 对这些流
seek
andtell
, 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 onifstream
andistringstream
(inhert fromistream
), 不能用seekg
. - 只能用 p version on
ostream
和 它的子类ofstream
andostringstream
.
- 只能用 g version on an
iostream
,fstream
,stringstream
可以用both types, either g or p version
- 定义了两对 seek and tell and distinguiushede by suffix: g 表示 getting, p 表示 putting
- 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.
- 比如
fstream
和stringstream
可以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
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_position
和offset
类型分别为pos_type
和off_type
. 都是machine-dependent types(各自defined inistream
和ostream
). pos_type
表示 file position andoff_type
表示offset from that position. value of typeoff_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
- Throwing an Exception
- Catching an Exception
- Function try Blocks and Constructors
- The noexcept Exception Specification
- Exception Specifications and Pointers, Virtuals, and Copy Control
- 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 followingthrow
不被执行. 而是 control is transferred fromthrow
to matchingcatch
.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 )继续寻找
- 否则. 如果该 try 嵌套在 其他的try 中, 检查 外层
- 如果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, ormain
function is exited without having found a matchingcatch
- 如果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.
- When a block is exited during stack unwinding, the compiler guarantees that objects created in that block are properly destroyed
- 如果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.
- 如果throw exception, 没有被catch,
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
- 因此, the expression in a
- 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.
- 因为local object may be detoryed before 若
- 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 放后面
- 因此, in a list of
- 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
- 通常上 catch all clause
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 acatch
or in a function called from acatch
- rethrow 不 specify an expression
- 如果改变parameter后 rethrow, 这些changes will be propagated only if catch exception declaration is a reference
- empty
比如下面例子只有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.
- The
- declaration:
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 specificationnoexcept
ande
itself does not contain athrow
. 否则 返回 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的
- 若 pointer 有 nonthrowing exception specification. 我们可以用pointer only to
- 如果virtual function 有nonthrowing 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)
- 如果any function invoked by synthesized member can throw, 则 sythesized member is
- 若无 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 namedwhat
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
, andbad_alloc
classes define a default constructor.runtime_error
andlogic_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 executinglogic_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
- Namespace Definitions
- Using Namespace Members
- Classes, Namespaces, and Scope
- Argument-Dependent Lookup and Parameters of Class Type
- Friend Declarations and Argument-Dependent Lookup
- Argument-Dependent Lookup and Overloading
- Overloading and using Declarations, Overloading and using Directives
- 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");
- 比如
- namespace 成员被外访问时 must indicate the namespace
- 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 incplusplus_primer
表示 definestd
nested insidecplusplus_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 keywordinline
可以写也可以不写 - inline namespace 通常用于 one release of an application to next. 例如put all code from current edition into an inline namesapce. Previous version 放进 noninlined namespaces
- keyword
- 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 Directive: using 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 ofblip
就像也被declare在global scope manip
与blip
中的j
有冲突, use the namej
, must explicitly 说明which version . Unqualifiedj
is ambiguous::j
is defined in global scope,blip::j
defined in namespace
- local declarations within
manip
hide namespacek
.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
insidef3
is okay, 因为f3
defined afterA::h
- E.g.2
- qualified name 说明了检查scope 顺序, reversed order
- E.g.2
A::C1::f3
: 先查 functionf3
, then classC1
, then namespaceA
- E.g.2
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 forcin
ands
defined 的 namespace (istream
andstring
), then compiler find thestring
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 usestd::>>
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
andforward
有 parameter type as rvalue reference which can match any type. 因此 自己定义move
no matter what type parameter has, 都会collide librarymove
, (forward
也是这样) - 因为
move
andforward
are specialized type manipulations. override behavior of these functions chance very small, And collision very likely. so usingstd::move
rather thanmove
表示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 forf
f
takes an argument of class type. andf
is implicitly declared in the same namespace asC
-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
- Multiple Inheritance
- Conversions and Multiple Base Classes
- Class Scope under Multiple Inheritance
- Virtual Inheritance
- 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 as
final
- 没有限制 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 { /* ... */ };
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
: 顺序如下
ZooAnimal
initialized firstBear
second direct base is initialized nextEndangered
most derived part is initialized lastPanda
最后
被cdostryed 的顺序
~Panda
~Enadangered
~Bear
~ZooAnimal
Error to inherit 有一样parameter list constructor from more than one base class: 因为Base1
和 Base2
都有 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
andBear/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
- 不管是第几个level in both subtree, 只要找到more in subtree, 就是ambugious. 比如 function defined in
- 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
- 如果name is found more than one base class, Unqualified uses of that name are ambiguous
例子, both ZooAnimal
和 Endangered
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
andRaccoon
class (intermediate base class) 需要specify virutal on their derivation fromZooAnimal
,
- 比如下面例子中,
- 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. 如果想 usex
through D object, 三种可能:- D1, D2 没有自己的
x
, access B 的x
no ambiguity - D1 or D2 定义了(override)
x
, access D1 or D2x
no ambiguity - D1 and D2 都定义了(override)
x
, accessx
: ambiguity : 解决方法是在 D中定义自己的x
(可以call D1, D2, or B 的x
by explicitly specify version 在自己的x
中 )
- D1, D2 没有自己的
例子一: 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 都继承
因为 Raccoon
和 Bear
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 howZooAnimal
base class is initialized, 即使ZooAnimal
is not a direct base ofPandas
- 上面例子中,
- 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, 顺序如下ZooAnimal
part constructed first, using initializers specified inPanda
constructor initializer list.- 注: 如果
ZooAnimal
没有explicitly initializeZooAnimal
base class, thenZooAnimal
default constructor is used.如果没有default constructor, code is error
- 注: 如果
Bear
part constructed nextRaccoon
part constructed nextEndangered
nextPanada
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 ZooAnimal
和 ToyAnimal
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
- Overloading new and delete
- malloc and free Functions
- 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 operatordelete[]
.
- destructor is run on the object pointer points or on the elements in the array to which
- 可以定义自己的
new
anddelete
, we take over responsibility for all dynamic memory allocation. Functions 必须correct- 可以定义在global scope or as member functions
- 当compiler 发现
new
ordelete
, 如果object class type, compiler 首先looks class scope 包括 base classes. 否则, look in global scope. . 如果没有找到, use library version - 可以用scope operator to force
new
ordelete
to use specfic version. 比如::new
look only in global scope rather than class scope
- Library 定义了8个 overloaded versions of
operator new
anddelete
. 前四个有可能throwbad_alloc
exception. 后四个support nonthrowing versions ofnew
nothrow_t
是struct 定义在new
header(also defines aconst
object namednothrow
, user pass nonthrowing version ofnew
)delete
must not throw an exception. bynoexcept
exception specifier- 如果定义下面8个, 必须定义在global scope or class scope.
- 当定义在class member, these operator are implicitly static(不同显示声明它static)
- static 原因:
new
anddelete
functions 必须是 static 因为they are used either before object is constructed or after it has been destroyed. 因此new
和delete
不能manipulate member data
new
operator new
used when we allocate an object,operator new []
is called when we allocate an array.operator new
oroperator new []
function must have a return type ofvoid*
且first argument 必须是size_t
,size_t
不能有default argument.- 当call
opeartor new
, initializesize_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 ofnew
比如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 typevoid*
(initialize with a pointer to the memory to free) operator delete
oroperator delete[]
当被定义成class member, function 可以有第二个parameter of typesize_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=
, 没有overloadnew
ordelete
expressions. In fact, we cannot redefine the behavior of the new and delete expressions.new
: always executes by calling anoperator new
to obtain memory then construct an object in memorydelete
expression always executes by destroying an object then callingoperator delete
to free memory used by an object- 通过提供定义
operator new
andoperator delete
, 可以change how memory is allocated. 可是, 不能改变basic meaning ofnew
anddelete
operator
- 自己定义了
operator new
andoperator 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
andfree
inherits from C. 定义在cstdlib
malloc
takes asize_t
说明 how many bytes to allocate. returns a pointer to memory it allocated , or 0 if was unable to allocate the memory.free
takes avoid*
that is a copy of a pointer that was return frommalloc
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
- can pass an address,
- 当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 sameallocator
object - pointer pass to placement new need not point to memory allocated by
operator new
- the pointer pass to
- 就像placement new 像使用
allocate
, an explicit call to a destructor is analogous to callingdestroy
- 像
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 expressiondynamic_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.
- The dynamic_cast Operator
- The typeid Operator
- Using RTTI
- 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
- if a dynamic_cast to a pointer type fails, result is
- 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 toDerived
object, cast initialiedp
to point to theDerived
object to whichbp
points. Safe io useDerived
operations inside if - 否则, result of cast is
0
, then condition fails, else process appropriate toBase
- 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 intypeinfo
library header
- 当cast to reference type fails, cast throws a
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 intypeinfo
header), or a type publicly derived fromtype_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)
anda
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 pointertypeid(*p)
, if p is a null pointer, throws abad_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 virtualequal
function. - 每个class 定义自己的
equal
to compare the data of its own type. operator takeBase&
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 intypeinfo
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 throughtypeid
operator
- cannot define, copy, assign objects of type
name
member function returns a C-style character string for the name of type represented bytype_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
- The only guarantee for
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 ( equivalentlyenum 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
- 只能define objects only as part of the
- scope enumeration using
- 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 ascase
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)
- 例如, define
- 只要 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
- Pointers to Data Members
- Pointers to Member Functions
- 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 ofclassname
- 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
- initialize: apply address-of operator(
- 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:
- They dereference the pointer to member to get the member that we want
- 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 asmyScreen.*(pmf())
: use functionpmf
return value as operand of pointer-to-member operator.*
因为pmf
not function, error
- 没有 paratheses,
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
- member function execute is passed to the implicit
- 方法二: Using
mem_fn
to Generate a Callablefunction
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
likefunction
, 定义在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 bybind
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.
- no connection between objects of enclosing class and object of nested 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
. TheToken
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
- 比如we add string member to union, 定义 union as anonymous union and make it member of a class
下面例子
- 定义一个 unnamed, unscoped enumeration, define
tok
as unnamed enum type- use
tok
as our discriminant. 当 union hold int, tok have valueINT
, when union has string, tok isSTR
- use
- 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
- Bit-fields
- volatile Qualifier
- 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 目的各不相同, 对这个问题回答与使用目的相关
- synthesized copy/move and assignment operators cannot be used to initialize or assign from a volatile object
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
- Unlike normal pointers, a function pointer points to code, not data. Typically function pointer stores the start of executable code
- Unlike normal pointers, we do not allocate de-allocate memory using function pointers. 不用allocate de-allocate memory
- 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
orvoid (*fucPtr)() = &fun
, 用不用&
一样的 - We can have array of function pointers. 必须function parameter 和 return type 都是一样的
- 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).