class B
{
Explict B(int x=0;bool b=ture);
};
doSomething(B(10)) //发生显式类型转换,B的构造函数将int 显式转换
c语言的四个次语言:
对于单纯常量,最好以const对象或enums替换#defines
对于形似函数的宏(macros),最好改用inline函数替换#defines
#define CALL_MAX(a,b) func((a)>(b)?(a):(b))
//改为
template
Inline void callMax(const T&a,const T&b)
{
Func((a)>(b)?(a):(b));
}
const char* const p =
Vectorconst_iterator it=
const Vectoriterator it=
不需要改动的值最好加const 省去后续麻烦
静态成员函数 不可访问 非静态成员变量,除非 在静态成员变量的数据类型前 加mutable。加完之后静态成员函数 可以对非静态成员进行操作
const_cast constexpression
//表示将const 转除,即 const 转为 non-const
注:new_type为目标数据类型,expression为原始数据类型变量或者表达式。
// 初始化a
int a=0;
//初始化b
double b;
std::cin>>b;
class Teacher
{
public:
Teacher(const string name,const int age):m_name(name),m_age(age),deptWork_count(0)
{ }
string m_name;
int m_age;
int deptWork_count;
};
class Teacher{ ·····};
Teacher& T()
{
static Teacher t;
return t//返回指向“static对象”的reference,而不是自身。
}
编译器可以暗自为class创建default构造函数、copy assignment 操作符(“ = ”操作符重载)、析构函数
class Home{
public:
/.../
private: //只声明不实现 ,拷贝构造函数与操作符重载
Home(const Home&);
Home operator=(const Home&);
};
class Home{
public:
Home(){};
~Home(){};
private: // ,拷贝构造函数与操作符重载
Home(const Home&);
Home operator=(const Home&);
};
class HomeTV :private Home { //TV类不再声明防拷贝相关函数。
/.../
};
C++并不禁止析构函数吐出异常,但它不鼓励你这样做。
返回左侧对象
class Widget {
public:
Widget& operator= (const Widget& rhs) // 返回类型是个reference,指向当前对象
{
return *this;
}
Widget& operator+= (const Widget& rhs) // 这个协议适用于+=、-=、*=等等
{
return *this;
}
Widget& operator= (int rhs) // 此函数也适用,即使此一操作符的参数类型不符协定
{
return *this;
}
class Widget11 {
public:
void swap(Widget11& rhs) {} // 交换*this和rhs的数据
Widget11& operator= (const Widget11& rhs)//可将& 去掉,函数内同时不实现交换操作,
//在构造过程中调用拷贝构造进行,这样会使得程序高效,
//但牺牲了清晰性
{
Widget11 temp(rhs); // 为rhs数据制作一份复件(副本)
swap(temp); // 将*this数据和上述复件的数据交换
return *this;
}
};
int test_item_11() //要避免这样! 采用上述swap代替赋值
{
Widget11 w;
w = w; // 赋值给自己
return 0;
}
在operator=函数内手工排列语句(确保代码不但”异常安全”而且”自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。
请记住:
void logCall(const std::string& funcName) {} // 制造一个log entry
class Customer {
public:
Customer(const Customer& rhs)
: name(rhs.name) // 复制rhs的数据
{
logCall("Customer copy constructor");
}
Customer& operator= (const Customer& rhs)
{
logCall("Customer copy assignment operator");
name = rhs.name; // 复制rhs的数据
return *this;
}
private:
std::string name;
};
class PriorityCustomer : public Customer {
public:
PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs), // 调用base class的copy构造函数
priority(rhs.priority)
{
logCall("PriorityCustormer copy constructor");
}
PriorityCustomer& operator= (const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs); // 对base class成分进行赋值操作
priority = rhs.priority;
return*this;
}
private:
int priority;
};
如果你为class添加一个成员变量,你必须同时修改copying函数(包括copy构造函数和copy assignment操作符)。(你也需要修改class的所有构造函数以及任何非标准形式的operator=。如果你忘记,编译器不太可能提醒你。)
任何时候只要你承担起”为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。那些成分往往是private,所以你无法直接访问它们,你应该让derived class的copying函数调用相应的base class函数。
令copy assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象。令copy构造函数调用copy assignment操作符同样无意义。构造函数用来初始化新对象,而assignment操作符只施行于已初始化对象身上。
如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。
把资源放进对象内,我们便可依赖C++的”析构函数自动调用机制”确保资源被释放。
auto_ptr是个”类指针(pointer-like)对象”,也就是所谓的”智能指针”,其析构函数自动对其所指对象调用delete。一定要注意别让多个auto_ptr同时指向同一对象,如果真是那样,对象会被删除一次以上。注:在C++11中auto_ptr已经被废弃,用unique_ptr替代。
std::unique_ptr用法参考:https://blog.csdn.net/fengbingchun/article/details/52203664
auto_ptr的替代方案是”引用计数型智慧指针”(reference-counting smart pointer, RCSP)。所谓RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。类似于C++11中的shared_ptr。
std::shared_ptr用法参考:https://blog.csdn.net/fengbingchun/article/details/52202007
请记住
请记住:
请记住:
int test_item_16()
{
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
delete stringPtr1; // 删除一个对象
delete [] stringPtr2; // 删除一个由对象组成的数组
typedef std::string AddressLines[4]; // 每个人的地址有4行,每行是一个string
std::string* pal = new AddressLines; // 注意:"new AddressLines"返回一个string*,就像"new string[4]"一样
//delete pal; // 行为未有定义
delete [] pal; // 很好
return 0;
}
当你使用new(也就是通过new动态生成一个对象),有两件事发生:第一,内存被分配出来(通过名为operator new的函数);第二,针对此内存会有一个(或更多)构造函数被调用。当你使用delete,也有两件事发生:针对此内存会有一个(或更多)析构函数被调用,然后内存才被释放(通过名为operator delete的函数)。
当你对着一个指针使用delete,唯一能够让delete知道内存中是否存在一个”数组大小记录”的办法就是:由你来告诉它。如果你使用delete时加上中括号(方括号),delete便认定指针指向一个数组,否则它便认定指针指向单一对象。
如果你调用new时使用[],你必须在对应调用delete时也使用[]。如果你调用new时没有使用[],那么也不该在对应调用delete时使用[]。
最好尽量不要对数组形式做typedef动作。因为C++标准程序库含有string、vector等template,可将数组的需求降至几乎为零。
class Widget17 {};
int priority() { return 0; }
void processWidget(std::shared_ptr<Widget17> pw, int priority) {}
int test_item_17()
{
// 执行new Widget17; 调用priority; 调用std::shared_ptr构造函数,它们的执行顺序不确定
processWidget(std::shared_ptr<Widget17>(new Widget17), priority()); // 可能泄露资源
std::shared_ptr<Widget17> pw(new Widget17); // 在单独语句内以智能指针存储newed所得对象
processWidget(pw, priority()); // 这个调用动作绝不至于造成泄露
return 0;
}
如果你有个对象属于内置类型(例如int),pass by value往往比pass by reference的效率高些。
class Rational {
public:
Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator)
{}
private:
int n, d; // 分子(numerator)和分母(denominator)
// 返回const Rational可以预防"没意思的赋值动作": Rational a, b, c; (a * b) = c;
friend const Rational operator* (const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
};
friend const Rational operator* (const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);//警告!!!糟糕的代码
return result
}
所谓reference只是个名称,代表某个既有对象。任何时候看到一个reference声明式,你都应该立刻问自己,它的另一个名称是什么?因为它一定是某物的另一个名称。任何函数如果返回一个reference指向某个local对象,都将一败涂地。(如果函数返回指针指向一个local对象,也是一样。)
请记住:宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性、包裹弹性(packaging flexibility)和机能扩充性。
class Rational24 {
public:
Rational24(int numerator = 0, int denominator = 1) {} // 构造函数刻意不为explicit,允许int-to-Rational24隐式转换
int numerator() const { return 1; } // 分子(numerator)的访问函数
int denominator() const { return 2; } // 分母(denominator)的访问函数
/*const Rational24 operator* (const Rational24& rhs) const
{
return Rational24(this->n * rhs.numerator(), this->d * rhs.denominator());
}*/
private:
int n, d;
};
const Rational24 operator* (const Rational24& lhs, const Rational24& rhs) // non-member函数
{
return Rational24(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
int test_item_24()
{
Rational24 oneEighth(1, 8);
Rational24 oneHalf(1, 2);
Rational24 result = oneHalf * oneEighth; // 很好
result = result * oneEighth; // 很好
result = oneHalf * 2; // 很好,隐式类型转换(implicit type conversion)
result = 2 * oneHalf; // 错误, only non-member function success
// 以对应的函数形式重写上述两个式子
//result = oneHalf.operator*(2); // 很好, only member function success
//result = 2.operator*(oneHalf); // 错误, only non-member function success
result = operator*(2, oneHalf); // 错误, only non-member function success
return 0;
}
无论何时如果你可以避免friend函数就该避免。
class WidgetImpl { // 针对Widget25数据而设计的class
public:
private:
int a, b, c; // 可能有许多数据,意味复制时间很长
std::vector<double> v;
};
class Widget25 { // 这个class使用pimpl(pointer to implementation)手法
public:
Widget25(const Widget25& rhs) {}
Widget25& operator= (const Widget25& rhs) // 复制Widget25时,令它复制其WidgetImpl对象
{
*pImpl = *(rhs.pImpl);
return *this;
}
void swap(Widget25& other)
{
using std::swap;
swap(pImpl, other.pImpl); // 若要置换Widget25就置换其pImpl指针
}
private:
WidgetImpl* pImpl; // 指针,所指对象内含Widget25数据
};
// std::swap针对Widget25特化版本
namespace std {
template<>
void swap<effective_cplusplus_::Widget25>(effective_cplusplus_::Widget25& a, effective_cplusplus_::Widget25& b)
{
a.swap(b); // 若要置换Widget25,调用其swap成员函数
}
}
所谓swap(置换)两对象值,意思是将两对象的值彼此赋予对方。缺省情况下swap动作可由标准程序库提供的swap算法完成。
一般而言,重载function template没有问题,但std是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的template,但不可以添加新的template(或class或function或其它任何东西)到std里头。
首先,如果swap的缺省实现码对你的class或class template提供可接受的效率,你不需要额外做任何事。其次,如果swap缺省实现版的效率不足,试着做以下事情:
(1). 提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。这个函数不该抛出异常。
(2). 在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
(3). 如果你正在编写一个class(而非class template),为你的class特化std::swap。并令它调用你的swap成员函数。 最后,如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。
如果一此赋值操作成本 低于 一次构造 + 析构的成本,则采用A 否在采用B。但是A做法由于一次构造作用域覆盖整个循环,对于后续代码可读性和维护性造成影响。一般采用B。
你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。如果这样,不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。另外能够提高程序效率和代码清晰度。
C++引入4个新型转型操作符的详细介绍.
const_cast通常被用来将对象的常量性移除(cast away the constness)。它也是唯一有此能力的C+±style转型操作符。
dynamic_cast主要用来执行”安全向下转型”(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int。
static_cast用来强迫隐式转换(implicit conversions),例如将non-const对象转为const对象,或将int转为double等等。它也可以用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但它无法将const转为non-const,这个只有const_cast才办得到。
class Point { // 这个class用来表述"点"
public:
Point(int x, int y) {}
void setX(int newVal) {}
void setY(int newVal) {}
};
struct RectData { // 这些"点"数据用来表现一个矩形
Point ulhc; // ulhc = "upper left-hand corner"(左上角)
Point lrhc; // lrhc = "lower right-hand corner"(右上角)
};
class Rectangle {
public:
Rectangle(const Point&, const Point&) {}
Point& upperLeft() const { return pData->ulhc; } //不允许!!这样返回一个内部指针,用户可以对某些数据进行修改
Point& lowerRight() const { return pData->lrhc; } //不允许!!这样返回一个内部指针,用户可以对某些数据进行修改
const Point& upperLeft() const { return pData->ulhc; } //允许!!用户不可更改 Point 类型指针指向的内容
const Point& lowerRight() const { return pData->lrhc; }//允许!!用户不可更改 Point 类型指针指向的内容
// 有了这样的改变,客户可以读取矩形的Point,但不能涂写它们
// 但即使如此,也可能导致dangling handles(空悬的号码牌):这种handles所指东西(的所属对象)不复存在
private:
std::shared_ptr<RectData> pData;
};
int test_item_28()
{
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2); // rec是个const矩形,从(0,0)到(100,100)
// upperLeft的调用者能够使用被返回的reference(指向rec内部的Point成员变量)来更改成员,但rec其实应该是不可变的(const)
rec.upperLeft().setX(50); // 现在rec却变成从(50,0)到(100,100)
return 0;
}
“异常安全”有两个条件:(1).不泄漏任何资源。(2).不允许数据败坏。
异常安全函数(Exception-safe functions)提供以下三个保证之一:
(1).基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。然而程序的现实状态(exact state)恐怕不可预料。
(2).强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数需有这样的认知:如果函数成功,就是完全成功;如果函数失败,程序会回复到”调用函数之前”的状态。
(3).不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型(例如int,指针等等)身上的所有操作都提供nothrow保证。这是异常安全码中一个必不可少的关键基础材料。
异常安全码(Exception-safe code)必须提供上述三种保证之一。如果它不这样做,它就不具备异常安全性。
有个一般化的设计策略很典型地会导致强烈保证,这个策略被称为copy and swap。原则很简单:为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)。但一般而言它并不保证整个函数有强烈的异常安全性。(因为当函数中也有调用其他函数,不能保证调用时不发生异常)
inline void f() {} // 假设编译器有意愿inline“对f的调用”
int test_item_30()
{
void (*pf)() = f; // pf指向f
f(); // 这个调用将被inlined,因为它是一个正常调用
pf(); // 这个调用或许不被inlined,因为它通过函数指针达成
return 0;
}
inline函数背后的整体观念是,将”对此函数的每一个调用”都以函数本体替换之。
过度热衷inlining会造成程序体积太大(对可用空间而言)。即使拥有虚内存,inline造成的代码膨胀亦会导致额外的换页行为(paging),降低指令高速缓存装置的击中率(instruction cache hit rate),以及伴随这些而来的效率损失。
inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内。这样的函数通常是成员函数。friend函数如果被定义于class内,它们也是被隐喻声明为inline。明确声明inline函数的做法则是在其定义式前加上关键字inline。
inline函数通常一定被置于头文件内,因为大多数建置环境(build environments)在编译过程中进行inlining,而为了将一个”函数调用”替换为”被调用函数的本体”,编译器必须知道那个函数长什么样子。
inlining在大多数C++程序中是编译期行为。
templates通常也被置于头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子。(这其实也不是世界一统的准则。某些建置环境可以在链接期才执行template具现化。只不过编译期完成具现化动作比较常见。)template的具现化与inlining无关。
大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空。
一个表面上看似inline的函数是否真是inline,取决于你的建置环境,主要取决于编译器。大多数编译器提供了一个诊断级别:如果它们无法将你要求的函数inline化,会给你一个警告信息。为数个inling函数声明entshit rate
编译器通常不对”通过函数指针而进行的调用”实施inlining,这意味对inline函数的调用有可能被inlined,也可能不被inlined,取决于该调用的实施方式。
实际上构造函数和析构函数往往是inlining的糟糕候选人。
inline函数无法随着程序库的升级而升级。换句话说如果f是程序库内的一个inline函数,客户将”f函数本体”编进其程序中,一旦程序库设计者决定改变f,所有用到f的客户端程序都必须重新编译。
一开始先不要将任何函数声明为inline,或至少将inlining施行范围局限在那些”一定成为inline”或”十分平淡无奇”的函数身上。慎重使用inline便是对日后使用调试器带来帮助。
标准程序库组件不该被前置声明。
(1). 如果使用object references或object pointers可以完成任务,就不要使用objects。你可以只靠一个类型声明式就定义出指向该类型的references和pointers;但如果定义某类型的objects,就需要用到该类型的定义式。
(2). 如果能够,尽量以class声明式替换class定义式。注意,当你声明一个函数而它用到某个class时,你并不需要该class的定义;纵使函数以by value方式传递该类型的参数(或返回值)亦然。
(3). 为声明式和定义式提供不同的头文件。为了促进严守上述准则,需要两个头文件,一个用于声明式,一个用于定义式。当然,这些文件必须保持一致性,如果有个声明式被改变了,两个文件都得改变。
Handle classes(使用pimpl idiom(pimpl是”pointer to implementation”的缩写))和Interface classes(特殊的abstract base class(抽象基类))解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性(compilation dependencies)。
Handle classes
在Handle classes身上,成员函数必须通过implementation pointer取得对象数据。那会为每一次访问增加一层间接性。而每一个对象消耗的内存数量必须增加implementation pointer的大小。最后,implementation pointer必须初始化(在Handle class构造函数内),指向一个动态分配得来的implementation object,所以你将蒙受因动态内存分配(及其后的释放动作)而来的额外开销,以及遭遇bad_alloc异常(内存不足)的可能性。
interface classes
至于interface classes,由于每个函数都是virtual,所以你必须为每次函数调用付出一个间接跳跃(indirect jump)成本。此外interface class派生的对象必须内含一个vptr(virtual table pointer),这个指针可能会增加存放对象所需的内存数量----实际取决于这个对象除了interface class之外是否还有其它virtual函数来源。
最后,不论handle classes或interface classes,一旦脱离inline函数都无法有太大作为。函数本体为了被inlined必须(很典型地)置于头文件内,但handle classes和interface classes正是被设计用来隐藏实现细节如函数本体。
然而,如果只因为若干额外成本便不考虑handle classes和interface classes,将是严重的错误
public inheritance(公开继承)意味”is-a”(是一种)的关系。
如果你令class D(“Derived”)以public形式继承class B(“Base”),你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。
请记住:
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int) {}
virtual void mf2() {}
void mf3() {}
void mf3(double) {}
};
class Derived : public Base {
public:
virtual void mf1() {}
void mf3() {}
void mf4() {}
};
class Derived33 : public Base {
public:
// 必须为那些原本会被遮掩的每个名称引入一个using声明式,否则某些你希望继承的名称会被遮掩
using Base::mf1; // 让Base class内名为mf1和mf3的所有东西在Derived作用域内都可见(并且public)
using Base::mf3;
virtual void mf1() {}
void mf3() {}
void mf4() {}
};
int test_item_33()
{
Derived d;
int x = 0;
d.mf1(); // 没问题,调用Derived::mf1
//d.mf1(x); // 错误,因为Derived::mf1遮掩了Base::mf1
d.mf2(); // 没问题,调用Base::mf2
d.mf3(); // 没问题,调用Derived::mf3
// Derived内的函数mf3遮掩了一个名为mf3但类型不同的Base函数
//d.mf3(x); // 错误,因为Derived::mf3遮掩了Base::mf3
Derived33 d2;
d2.mf1(); // 仍然没问题,仍然调用Derived::mf1
d2.mf1(x); // 现在没问题了,调用Base::mf1
d2.mf2(); // 仍然没问题,调用Base::mf2
d2.mf3(); // 没问题,调用Derived::mf3
d2.mf3(x); // 现在没问题了,调用Base::mf3
return 0;
}
请记住:
(1). derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
(2).为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。
成员函数的接口总是会被继承。
声明一个pure virtual函数的目的是为了让derived classes只继承函数接口。
声明简朴的(非纯)impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。
请记住:
(1).接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
(2). pure virtual函数只具体指定接口继承。
(3). 简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承。(4). non-virtual函数具体指定接口继承以及强制性实现继承。
NVI手法实现Template Method模式
class GameCharacter {
public:
int healthValue() const // derived classes不重新定义它
{
// ... // 做一些事前工作
int retVal = doHealthValue(); // 做真正的工作
// ... // 做一些事后工作
return retVal;
}
private:
virtual int doHealthValue() const // derived classes可重新定义它
{
return 10;
}
};
/*********************************************************************************/
// Strategy设计模式的简单应用
class GameCharacter35; // 前置声明(forward declaration)
int defaultHealthCalc(const GameCharacter35& gc) { return 1; }
class GameCharacter35 {
public:
typedef int (*HealthCalcFunc)(const GameCharacter35&);
//(HealthCalcFunc hcf = defaultHealthCalc)不给定实参也能调用,或者给定一个默认参数hcf,两者产生的对象相同
explicit GameCharacter35(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {}
int healthValue() const
{
return healthFunc(*this);
}
private:
HealthCalcFunc healthFunc;
};
class EvilBadGuy : public GameCharacter35 {
public:
explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter35(hcf) {}
};
int loseHealthQuickly(const GameCharacter35&) { return 2; } // 健康指数计算函数1
int loseHealthSlowly(const GameCharacter35&) { return 3; } // 健康指数计算函数2
/*********************************************************************************/
// 藉由std::function完成Strategy模式
class GameCharacter35_1;
int defaultHealthCalc_1(const GameCharacter35_1& gc) { return 11; }
class GameCharacter35_1 {
public:
// HealthCalcFunc可以是任何"可调用物"(callae entity),可被调用并接受
// 任何兼容于GameCharacter35_1之物,返回任何兼容于int的东西
typedef std::function<int (const GameCharacter35_1&)> HealthCalcFunc;
explicit GameCharacter35_1(HealthCalcFunc hcf = defaultHealthCalc_1) : healthFunc(hcf) {}
int healthValue() const
{
return healthFunc(*this);
}
private:
HealthCalcFunc healthFunc;
};
int test_item_35()
{
EvilBadGuy ebg1(loseHealthQuickly); // 相同类型的人物搭配不同的健康计算方式
EvilBadGuy ebg2(loseHealthSlowly);
return 0;
}
藉由Non-Virtual Interface手法实现Template Method模式:这一基本设计,也就是”令客户通过public non-virtual成员函数间接调用private virtual函数”,称为non-virtual interface(NVI)手法。它是所谓Template Method设计模式(与C++ templates并无关联)的一个独特表现形式。把这个non-virtual函数称为virtual函数的外覆器(wrapper)。在NVI手法下其实没有必要让virtual函数一定得是private。
Template Method模式介绍参考:https://blog.csdn.net/fengbingchun/article/details/30990475
藉由Function Pointers实现Strategy模式。
Strategy模式介绍参考:https://blog.csdn.net/fengbingchun/article/details/32179281
std::function介绍参考:https://blog.csdn.net/fengbingchun/article/details/52562918
当你为解决问题而寻找某个设计方法时,不妨考虑virtual函数的替代方案:它们各自有其相对的优点和缺点
(1). 使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式。它以public non-virtual成员函数包裹较低访问性(private或protected)的virtual函数。
(2). 将virtual函数替换为”函数指针成员变量”,这是Strategy设计模式的一种分解表现形式。
(3). 以std::function成员变量替换virtual函数,因而允许使用任何可调用物(callable entity)搭配一个兼容于需求的签名式。这也是Strategy设计模式的某种形式。
(4). 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现方法。
请记住:
(1). virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
(2). 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
(3). std::function对象的行为就像一般函数指针。这样的对象可接纳”与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。
class B {
public:
void mf() { fprintf(stdout, "B::mf\n"); }
};
class D : public B {
public:
void mf() { fprintf(stdout, "D::mf\n"); } // 遮掩(hides)了B::mf
};
int test_item_36()
{
D x;
B* pB = &x;
// 由于pB被声明为一个pointer-to-B,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为"B派生之class"的对象
pB->mf(); // 调用B::mf
D* pD = &x;
pD->mf(); // 调用D::mf
return 0;
}
请记住:绝对不要重新定义继承而来的non-virtual函数。
class Shape37 {
public:
enum ShapeColor { Red, Green, Blud };
// 所有形状都必须提供一个函数,用来绘出自己
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle37 : public Shape37 {
public:
// 注意,赋予不同的缺省参数值。这真糟糕!
virtual void draw(ShapeColor color = Green) const
{
fprintf(stdout, "rectangle shape color: %d\n", color);
}
};
class Circle37 : public Shape37 {
public:
virtual void draw(ShapeColor color) const
// 请注意,以上这么写则当客户以对象调用此函数,一定要指定参数值。因为静态绑定下这个函数并
// 不从其base继承缺省参数值。但若以指针(或reference)调用此函数,可以不指定参数值,因为动态
// 绑定下这个函数会从其base继承缺省参数值
{
fprintf(stdout, "circle shape color: %d\n", color);
}
};
int test_item_37()
{
Shape37* ps; // 静态类型为Shape37*;没有动态类型,因为它尚未指向任何对象
Shape37* pc = new Circle37; // 静态类型为Shape37*,动态类型为Circle37*
Shape37* pr = new Rectangle37; // 静态类型为Shape37*,动态类型为Rectangle37*
pc->draw(Shape37::Red); // 调用Circle37::draw(Shape37::Red)
pr->draw(Shape37::Red); // 调用Rectangle::draw(Shape37::Red)
// 即使把指针换成references问题仍然存在
pr->draw(); // 调用Rectangle37::draw(Shape37::Red)!
return 0;
}
virtual函数系动态绑定(dynamically bound),而缺省参数值确是静态绑定(statically bound)。静态绑定又名前期绑定,early binding;动态绑定又名后期绑定,late binding。
对象的所谓静态类型(static type),就是它在程序中被声明时所采用的类型。对象的所谓动态类型(dynamic type)则是指”目前所指对象的类型”。也就是说,动态类型可以表现出一个对象将会有什么行为。virtual函数系动态绑定而来,意思是调用一个virtual函数时,究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型。
请记住:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数----你唯一应该覆写的东西----确是动态绑定。
class Address {};
class PhoneNumber {};
class Person {
private:
std::string name; // 合成成分物(composed object)
Address address; // 同上
PhoneNumber voiceNumber; // 同上
PhoneNumber faxNumber; // 同上
};
复合(composition)是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。
public继承带有is-a(是一种)的意义。复合也有它自己的意义。实际上它有两个意义。复合意味has-a(有一个)或is-implemented-in-terms-of(根据某物实现出)。
请记住:
(1). 复合(composition)的意义和public继承完全不同。
(2). 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。
class Empty39 {};
class HoldsAnInt {
private:
int x;
Empty39 e;
};
class HoldsAnInt39 : private Empty39 { // EBO(empty base optimization)
private:
int x;
};
int test_item_39()
{
fprintf(stdout, "sizeof(Empty39): %d\n", sizeof(Empty39)); // 1
fprintf(stdout, "sizeof(HoldsAnInt): %d\n", sizeof(HoldsAnInt)); // 8
fprintf(stdout, "sizeof(HoldsAnInt39): %d\n", sizeof(HoldsAnInt39)); // 4
return 0;
}
如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。这和public继承的情况不同。由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protected或public属性。private继承意味implemented-in-terms-of(根据某物实现出)。如果你让class D以private形式继承class B,你的用意是为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。private继承纯粹只是一种实现技术。private继承意味只有实现部分被继承,接口部分应略去。如果D以private形式继承B,意思是D对象根据B对象实现而得,再没有其它意涵了。
尽可能使用复合,必要时才使用private继承。
EBO(empty base optimization, 空白基类最优化),EBO一般只在单一继承(而非多重继承)下才可行。
请记住:
(1). private继承意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
(2).和复合(composition)不同,private继承可以造成empty base最优化。这对致力于”对象尺寸最小化”的程序库开发者而言,可能很重要。
请记住:
(1).多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要。
(2).virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
(3).多重继承的确有正当用途。其中一个情节涉及”public继承某个Interface class”和”private继承某个协助实现的class”的两相组合。
// 隐式接口
template<typename T>
void doProcessing(T& w)
{
T someNastyWidget;
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
// 显示接口
class Widget41 {
public:
Widget41();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget41& other);
};
“以不同的template参数具现化function templates”会导致调用不同的函数,这便是所谓的编译期多态(compile-time polymorphism)。”哪一个重载函数该被调用”(发生在编译期)和”哪一个virtual函数该被绑定”(发生在运行期)。
通常显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。隐式接口就完全不同了,它并不基于函数签名式,而是由有效表达式(valid expressions)组成。
请记住:
(1).classes和templates都支持接口(interface)和多态(polymorphism)。
(2).对classes而言接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期。(3).对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
// 一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须
// 在紧临它的前一个位置放上关键字typename
typename C::const_iterator iter(container.begin());
}
}
template<typename C> //允许使用"typename"(或"class")
void f42(const C& container, // 不允许使用"typename",C并不是嵌套从属类型名称
typename C::iterator iter); // 一定要使用"typename",C::iterator是个嵌套从属类型名称
template<typename T>
class Base42 {
class Nested {
Nested(int x) {}
};
};
template<typename T>
class Derived42 : public Base42<T>::Nested { // base class list中不允许"typename"
public:
explicit Derived42(int x)
: Base42<T>::Nested(x) // mem.init.list中不允许"typename"
{
typename Base42<T>::Nested temp; // 嵌套从属类型名称,既不在base class list中也不在
// mem.init.list中,作为一个base class修饰符需加上typename
}
};
template<typename IterT>
void workWithIterator(IterT iter)
{
// 使用IterT对象所指物的相同类型将temp初始化为iter所指物
// 如果IterT是vector::iterator,temp的类型就是int
// value_type被嵌套于iterator_traits之内而IterT是个template参数,所以必须在它之前放置typename
typename std::iterator_traits<IterT>::value_type temp(*iter);
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp2(*iter);
}
当我们声明template类型参数,class和typename的意义完全相同。
template内出现的名称如果相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name)。嵌套从属类型名称(nested dependent type name),也就是个嵌套从属名称并且指涉某类型。
tepename只被用来验明嵌套从属类型名称,其它名称不该有它存在。
“typename必须作为嵌套从属类型名称的前缀词”这一规则的例外是,typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class修饰符。
请记住:
(1).声明template参数时,前缀关键字class和typename可互换。
(2).请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。