Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~
异常处理
概述
- 如何选handler:the one nearest in the call chain that matches the type of the thrown object
stack unwinding(栈展开)
- 含义:抛出异常时,将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否可以处理该异常。如果不能处理,就退出当前函数,并且释放当前函数的内存并销毁局部对象(class会调用析构函数,内置类型无需编译器处理即会销毁),继续到上层的调用函数中查找,直到找到一个可以处理该异常的catch或the main function itself is exited。这个过程称为栈展开(stack unwinding)。当处理该异常的catch结束之后,紧接着该catch之后的点继续执行。
- once an exception is raised, it cannot remain unhandled. If no matching catch is found, the program calls the library terminate function which stops execution of the program
- 如果一个块通过new动态分配内存,并且在释放该资源之前发生异常,该块因异常而退出,那么在栈展开期间不会释放该资源,编译器不会删除该指针,这样就会造成内存泄露
- During stack unwinding, an exception has been raised but is not yet handled. If a new exception is thrown during stack unwinding and not caught in the function that threw it, terminate is called. Because destructors may be invoked during stack unwinding, they should never throw exceptions that the destructor itself does not handle
- all of the standard library types guarantee that their destructors will not raise an exception
异常对象和throw expression
- compiler uses the thrown expression to copy initialize an exception object,所以the expression in a throw
- 必须是complete type(即声明且定义了)
- 若是类,则必须有an accessible destructor and an accessible copy or move constructor
- 若是array/函数类型,它会被converted to its corresponding pointer type
- 异常对象are in space, managed by the compiler, that is guaranteed to be accessible to whatever catch is invoked
- 异常对象 is destroyed after the exception is completely handled
- it is almost certainly an error to throw 一个指向局部变量的指针,因为throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides
- 异常对象的类型是编译时就确定的,和throw expression的静态类型相同,所以If a throw expression dereferences a pointer to a base-class type, and that pointer points to a derived-type object, then the thrown object is sliced down,即only the base-class part is thrown
catch exception
- catch的形参必须是complete type且不能是右值引用;对应的实参是异常对象
- catch的形参与实参的匹配非常严格,只允许以下几种conversion
- from nonconst to const:即a throw of a nonconst object can match a catch specified to take a reference to const
- from derived type to base type
- an array is converted to a pointer to the type of the array; a function is converted to the appropriate pointer to function type
- catch-all handlers:match所有类型的异常,通常与rethrow合用,先does whatever local work can be done 然后 rethrows the exception
void manip() {
try {
// actions that cause an exception to be thrown
}
catch (...) {
// work to partially handle the exception
throw;
}
}
rethrow
- 定义:a throw that is not followed by an expression
- 作用:does not specify an expression; the (current) exception object is passed up the chain
- 使用要求:can appear only in a catch or in a function called (directly or indirectly) from a catch
- If an empty throw is encountered when a handler is not active, terminate is called
- 栗子
catch (my_error &eObj) { // specifier is a reference type
eObj.status = errCodes::severeErr; // modifies the exception object
throw; // the status member of the exception object is severeErr
} catch (other_error eObj) { // specifier is a nonreference type
eObj.status = errCodes::badErr; // modifies the local copy only
throw; // the status member of the exception object is unchanged
}
function try block
- 使用场景:处理constructor initializer时异常可能出现,而constructor的函数体还未执行,所以constructor函数体中的catch语句不能catch该异常,所以要用function try block
- 定义associate a group of catch clauses with the initialization phase of a constructor (or the destruction phase of a destructor) as well as with the constructor’s (or destructor’s) function body
- 栗子:注意下面代码中的catch可以handle exceptions thrown either from within the member initialization list or from within the constructor body
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);
}
- 特别注意:function try block只处理构造函数开始执行后抛出的异常,所以初始化构造函数的形参时抛出的异常不属于function try block;与其他函数调用一样,初始化形参时抛出的异常属于调用函数的那句话所在的context
noexcept
- 是c++ 11新特性
- noexcept specifier is not part of a function’s type
- 栗子
void recoup(int) noexcept; // won't throw
void recoup(int) throw(); // 旧标准下的equivalent declaration
void alloc(int); // might throw
void recoup(int) noexcept(true); // recoup won't throw
void alloc(int) noexcept(false); // alloc can throw
void f() noexcept(noexcept(g())); // f has same exception specifier as g
- 要求:关键字noexcept must appear on all of the declarations and the corresponding definition of a function or on none of them;关键字noexcept需写在trailing return之前;我们可以specify noexcept on the declaration and definition of a function pointer;关键字noexcept不能出现在typedef or type alias中;在成员函数中,关键字noexcept follows any const or reference qualifiers, and it precedes final, override, or = 0 on a virtual function
- 作用:promises the callers of the nonthrowing function that they will never need to deal with exceptions
- 标注了noexcept的函数可能会抛出异常,因为编译器不检查这个性质;If a noexcept function does throw, terminate is called, thereby enforcing the promise not to throw at run time
// this function will compile, even though it clearly violates its exception specification
void f() noexcept { // promises not to throw any exception
throw exception(); // violates the exception specification
}
- 指向函数的指针与函数的匹配:声明为noexcept的函数指针只能指向noexcept的函数;而a pointer that specifies (explicitly or implicitly) that it might throw can point to any function
// 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
- 虚函数的noexcept性质:若基类虚函数是noexcept,则子类对应的虚函数必须是noexcept;若基类虚函数没有声明noexcept,则子类对应的虚函数可以是noexcept也可以不是
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
};
- synthesized member的noexcept性质:
- If all the corresponding operation for all the members and base classes promise not to throw, then the synthesized member is noexcept
- If any function invoked by the synthesized member can throw, then the synthesized member is noexcept(false)
- if we do not provide an exception specification for a destructor that we do define, the compiler synthesizes one for us
standard-library exception classes
- inheritance hierarchy一览
- 大家都定义了:copy constructor、copy-assignment operator、虚析构函数、a virtual member function named what(返回const char*,指向a null-terminated character array;guaranteed not to throw any exceptions)
- exception, bad_cast, and bad_alloc:还定义了default constructor
- runtime_error and logic_error:还定义了constructors that take a C-style character string or a library string argument;what函数returns the message used to initialize the exception object
- 栗子
// 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;
};
// 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
// use sum
} catch (const isbn_mismatch &e) {
cerr << e.what() << ": left isbn(" << e.left
<< ") right isbn(" << e.right << ")" << endl;
}
}
命名空间
定义命名空间
- 命名空间中可以定义的内容:classes, variables (with their initializations), functions (with their definitions), templates, and other namespaces
- 要求与性质
- a namespace name must be unique within the scope in which the namespace is defined
- namespaces may be defined at global scope or inside another namespace
- namespaces may not be defined inside a function or a class
- different namespaces may have members with the same name
- names defined in a namespace may be accessed directly by other members of the namespace, including scopes nested within those members
- 命名空间的定义可以是不连续的,所以一段命名空间的定义可能是新建命名空间,也可能adds to an existing one
- 和类一样,成员在命名空间内声明了之后,才能在命名空间外定义,且定义时需标明命名空间::
- 和类一样,我们通常separate interface and implementation of namespaces:头文件中放类的定义、函数/对象的声明,source file中放其他成员的定义
// ---- Sales_data.h---- // #includes should appear before opening the namespace #include <string> namespace cplusplus_primer { class Sales_data { /* ... */}; Sales_data operator+(const Sales_data&, const Sales_data&); // declarations for the remaining functions in the Sales_data interface } // ---- Sales_data.cc---- // be sure any #includes appear before opening the namespace #include "Sales_data.h" namespace cplusplus_primer { // definitions for Sales_data members and overloaded operators }
- 栗子
namespace cplusplus_primer {
class Sales_data { / * ... * /};
Sales_data operator+(const Sales_data&, const Sales_data&);
class Query { /* ... */ };
class Query_base { /* ... */};
} // like blocks, namespaces do not end with a semicolon
// namespace members defined outside the namespace must use qualified names
cplusplus_primer::Sales_data cplusplus_primer::operator+(const Sales_data& lhs, const Sales_data& rhs) {
Sales_data ret(lhs); // 已经在cplusplus_primer空间中了,所以不用::
// ...
}
nested namespace
- nested namespace中的名字可以与外层namespace中的名字重复,nested namespace中的名字会hide外层名字
global namespace
- ::member_name refers to a member of the global namespace
inline namespace
- c++ 11新特性
- 作用:names in an inline namespace can be used as if they were direct members of the enclosing namespace
- 栗子:因为FifthEd is inline,所以code that refers to cplusplus_primer:: will get the
version from that namespace,而以前版本的code也可以获得,比如cplusplus_primer::FourthEd::Query_base
inline namespace FifthEd {
// namespace for the code from the Primer Fifth Edition
}
namespace FifthEd { // implicitly inline,可以写/不写关键字inline
class Query_base { ... };
// other Query-related declarations
}
namespace FourthEd {
class Item_base { /* ... */};
class Query_base { /* ... */};
// other code from the Fourth Edition
}
namespace cplusplus_primer {
#include "FifthEd.h"
#include "FourthEd.h"
}
unnamed namespace
- 其中定义的变量有static lifetime:created before their first use and destroyed when the program ends
- each file has its own unnamed namespace;不同文件的unnamed namespace不相关
- If a header defines an unnamed namespace, the names in that namespace define different entities local to each file that includes the header
PS:在unnamed namespace出现之前,c++从c语言继承了这种方法:programs declared names as static to make them local to a file - names defined in an unnamed namespace are in the same scope as the scope at which the namespace is defined,变量等的名字不能重复:
int i; // global declaration for i
namespace {
int i;
}
// ambiguous: defined globally and in an unnested, unnamed namespace
i = 10;
namespace local {
namespace {
int i;
}
}
// ok: i defined in a nested unnamed namespace is distinct from global i
local::i = 42;
namespace alias
namespace primer = cplusplus_primer;
namespace Qlib = cplusplus_primer::QueryLib;
PS:一个namespace可以有多个别名
using
- using declaration
- 作用:make一个成员visible
- 可以用在:global / local / namespace / class scope(注意:在class scope中,using declaration只能refer to base class members)
- visible from the point of the using declaration to the end of the scope in which the declaration appears
- entities with the same name defined in an outer scope are hidden
- using directive
- 作用:make all the names from a specific namespace visible
- 可以用在:global / local / namespace scope
- visible from the point of the using directiveto the end of the scope in which the directive appears
- using declaration 与 using directive 的区别
- using declaration puts the name in the same scope as that of the using declaration itself
- using directive lifts the namespace members into the nearest scope that contains both the namespace itself and the using directive(因为a namespace might include definitions that cannot appear in a local scope)
- using directive的栗子一
// namespace A and function f are defined at global scope
namespace A {
int i, j;
}
void f() {
using namespace A; // injects the names from A into the global scope
cout << i * j << endl; // uses i and j from namespace A
// ...
}
- using directive的栗子二:members of blip appear as if they were defined in the scope in which both blip and manip are defined
namespace blip {
int i = 16, j = 15, k = 23;
// other declarations
}
int j = 0; // ok: j inside blip is hidden inside a namespace
void manip() {
// using directive; the names in blip are ''added'' to the global scope
using namespace blip; // clash between ::j and blip::j
// detected only if j is used
++i; // sets blip::i to 17
++j; // error ambiguous: global j or blip::j?
++::j; // ok: sets global j to 1
++blip::j; // ok: sets blip::j to 16
int k = 97; // local k hides blip::k
++k; // sets local k to 98
}
name lookup
- 栗子一
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
}
- 栗子二:A::C1::f3 indicate the reverse order in which the class scopes and namespace scopes are to be searched
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
friend
- when a class declares a friend, the friend declaration does not make the friend visible,但如果该友元函数的形参是an otherwise undeclared class or function,那么该友元函数和形参中定义的name都会被认为是 member of the closest enclosing namespace
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
}
overloading
- 若函数调用时,形参是一个类(设为A),那么在找重载函数的candidate时,也会到定义了A、A的基类的命名空间找candidate函数
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); // NS::display也是candidate
return 0;
}
- using declaration与重载
- when we write a using declaration for a function, all the versions of that function are brought into the current scope
using NS::print(int); // error: cannot specify a parameter list using NS::print; // ok: using declarations specify names only
- 若在local scope中写using declaration,则它会hide existing declarations for that name in the outer scope
- 若using declaration introduces 某函数,它和当前scope(即using declaration所在的scope)中的一个函数的名字和参数列表相同,则using declaration is in error
- 其他情况下,using declaration会增加重载函数的candidate
- using directive与重载
namespace AW {
int print(int);
}
namespace Primer {
double print(double);
}
// using directives create an overload set of functions from different namespaces
using 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;
}
- 若using directive introduces a function that has the same parameters as an existing function,不会报错,除非we try to call the function without specifying the version
- 其他情况下,using directive会增加重载函数的candidate
Multiple Inheritance
概述
- 含义:inherits the properties of all its parents
- a base class may appear only once in a given derivation list
- 图例
class Bear : public ZooAnimal { /* ... */ };
class Panda : public Bear, public Endangered { /* ... */ };
初始化与constructors
- derived constructors initialize all base classes,且初始化顺序与class derivation list中的顺序一致
// 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) { }
- 子类可以从多个基类继承constructor,但不能从多个基类继承多个形参列表相同的constructors
- 错误的栗子
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 };
- 正确的栗子
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 };
析构函数
- 子类的析构函数只负责cleaning up resources allocated by 子类
- 合成的析构函数函数体为空
- 析构顺序与构造顺序相反
Copy and Move Operations
- 合成的copy-control members中,each base class is implicitly constructed, assigned, or destroyed, using the corresponding member from that base class
- 自定义的copy/move constructors and assignment operators must copy, move, or assign the whole object
derived-base conversion
- a pointer or a reference to a derived class can be converted automatically to a pointer or a reference to an accessible base class
- 栗子
// 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
基类指针可见范围
- If we use a ZooAnimal pointer, only the operations defined in that class 和子类虚函数 are usable(the Bear-specific, Panda-specific, and Endangered portions of the Panda interface are invisible)
- 栗子(各类有的虚函数见下图)
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()
Class Scope under Multiple Inheritance
- the scope of a derived class is nested within the scope of its direct and indirect base classes
- name lookup happens simultaneously among all the direct base classes
- 若在多个基类中找到了该name,则use of that name is ambiguous,需要specify which version we want to use
- 栗子:若ZooAnimal 和 Endangered 都定义了一个名为 max_weight 的成员,且 Panda 未定义该成员,则下面的语句是错误的,应该用ZooAnimal::max_weight 或 Endangered::max_weight:
double d = ying_yang.max_weight();
PS:name lookup happens before type checking
Virtual Inheritance
使用场景
- 一个类不能在derivation list中include the same base class more than once,但一个类可以inherit from the same base class more than once(比如,iostream的两个基类istream、ostream都继承自basic_ios,iostream继承了两次basic_ios)
- 而a derived object contains a separate subpart corresponding to each class in its derivation chain,所以iostream会有两块basic_ios
- 但an iostream object wants to use the same buffer for both reading and writing;If an iostream object has two copies of its basic_ios class, this sharing isn’t possible
- 所以使用virtual inheritance,the derived object contains only one, shared subobject for that virtual base class
- 栗子
virtual继承关系的定义
- Panda has only one ZooAnimal base subpart
// 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 {
};
derived_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
Class Scope under Virtual Inheritance
- 若虚基类中的某成员 is overridden along 0/1 个 derivation path,那么该成员 can be accessed directly 而不产生 ambiguity
- 若虚基类中的某成员 is overridden along >=2 个 derivation path,那么在子类中直接调用该成员会造成 ambiguity
- 栗子:B有成员x,D1、D2虚继承自B,D继承自D1和D2,当我们通过D直接使用x时
- 若D1和D2都没有定义x,则没有歧义,x会被解析为B-part的成员;
- 若D1和D2其中的一个(不妨设为D1)定义了x,则没有歧义,x会被解析为D1-part的成员;
- 若D1和D2都定义了x,则有歧义
constructor
- the virtual base is initialized by the most derived constructor(比如when we create a Panda object, the Panda constructor alone controls how the ZooAnimal base class is initialized)
Panda::Panda(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Panda"),
Bear(name, onExhibit),
Raccoon(name, onExhibit),
Endangered(Endangered::critical),
sleeping flag(false) { }
- How a Virtually Inherited Object Is Constructed:先初始化虚基类-part,再按照derivation list的顺序初始化直接基类-part,最后初始化子类-part;以Panda的构造为例:
- the (virtual base class) ZooAnimal part is constructed first, using the initializers specified in the Panda constructor initializer list (若没有则调用ZooAnimal的默认构造函数)
- the Bear part is constructed
- the Raccoon part is constructed
- the Endangered part is constructed
- the Panda part is constructed
- Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy
- 若有多个虚基类,按照derivation list中的顺序构造,比如:
class Character { /* ... */ };
class BookCharacter : public Character { /* ... */ };
class ToyAnimal { /* ... */ };
class TeddyBear : public BookCharacter,
public Bear, public virtual ToyAnimal { /* ... */ };
// TeddyBear 的构造顺序
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
PS:synthesized copy and move constructors、synthesized assignment operators都遵从这种顺序