多态类型
多态类型在作为接口时有数据保护和隐藏的效果。
多态分为两种:通用多态和特定多态
通用多态:对工作的类型不加限制,允许对不同类型的值执行相同的代码
又分为参数多态(parametric)和包含多态(inclusion)
参数多态:采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。例如,模板类。
包含多态:同样的操作可用于一个类型及其子类型(注意是子类型,不是子类)。包含多态一般需要进行运行时的类型检查。例如,虚函数“virtual—override”机制。
特定多态:只对有限数量的类型有效,而且对不同类型的值可能要执行不同的代码
又分为过载多态(overloading)和强制多态(coercion)
- 强制多态:编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting)。举个例子,比如,int+double,编译系统一般会把int转换为double,然后执行double+double运算,这个int->double的转换,就实现了强制多态,即可是隐式的,也可显式转换。
- 过载(overloading)多态:同一个名(操作符﹑函数名)在不同的上下文中有不同的类型。程序设计语言中基本类型的大多数操作符都是过载多态的。通俗的讲法,就是c++中的函数重载。
PS:类(class)和类型(type)的区别
type包括两种:
基本类型
int、char、double、bool、unsigned等等
复合类型
class、struct、function、array数组、reference引用、union联合体、enum枚举类型。他们基本上都是一个type里面有很多其他的type。所以class是类型type的一种,type是一个抽象的概念。
cross-DLL problem
- 这个问题发生于“对象在一个DLL(动态链接程序库)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”会导致运行期错误。
- tr1::shared_ptr不会有该问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个DLL”的delete。例如:
//Stock类派生自Investment类
std::tr1::shared_ptr<Investment> CreateInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
//返回的那个tr1::shared_ptr可被传递给任何其它DLLs,无需在意“cross_DLL problem”。这个指向Stock的tr1::shared_ptrs会追踪记录“当Stock的引用次数变为0时该调用那个DLL's delete”
最常见的tr1::shared_ptr实现品来自Boost。
Boost的shared_ptr是原始指针的两倍,以动态分配内存作为薄记用途和“删除器之专属数据”,以virtual形式调用删除器,并在多线程程序修改引用次数时蒙受线程同步化的额外开销。(只要定义一个预处理器符号就可以关闭多线程支持)。总之,它比原始指针大且慢,而且使用辅助动态内存。在许多应用程序中这些额外的执行成本并不显著,然而其降低客户错误的成效却是每个人都看得到。
导入新类型来预防“接口被误用”
举例:
class Data{
public:
Data(int month,int day,int year);
...
};
...
Data d(30,3,1994);//wrong以错误的次序传递参数
Data d(2,30,1994);//wrong传递参数不符合要求
导入新类型:
struct day{
explicit Day(int d):val(d){}
...
int val;
};
struct month{
explicit month(int m):val(m){}
...
int val;
};
struct year{
explicit year(int y):val(y){}
...
int val;
};
class Data{
public:
Data(const month& m,const day& d,const year& y);
...
};
//调用
Data d(30,3,1995);//wrong
Data d(day(30),month(3),year(1995));//wrong
Data d(month(3),day(30),year(1995));//right
也可以在新类型中限制其值:
class month{
public:
static month Jan(){return month(1);}
static month Feb(){return month(2);}
...
private:
explicit month(int m);
...
};
Data d(month::mar(),day(30),year(1993));
定义一个新的type需要考虑的问题:
(定义一个新的class也就是定义一个新的type)
新type的对象应该如何被创建和销毁?
对象的初始化和对象的赋值该有什么样的差别?
构造函数和赋值操作符的差异
新type的对象如果被passed by value(以值传递),意味着什么?
copy构造函数用来定义一个type的passed by value该如何实现
新type的“合法值”
新type需要配合某个继承图系吗?
如果你继承自某些既有的classes,就受到那些classes的设计的束缚,特别是受到其函数是virtual或non-virtual的影响;
如果你允许其他classes继承你的class,那会影响你所声明的函数-尤其是析构函数是否为virtual。
新type需要什么样的转换?
什么样的标准函数应该驳回?(声明为private者)
谁该取用新type的成员?
帮助你决定哪个成员为public,哪个为protected,哪个为private
什么是新type的“未声明接口”?
新的type有多么一般化?
如果你定义的是一整个types家族,则应该定义一个新的class template。
by reference-to-const代替by value
“昂贵”的by value
缺省情况下C++以by value方式传递对象至函数。函数参数都是以实际实参的复件为初值,而调用端所获得的也是函数返回值的一个复件。这些复件由对象的copy构造函数产生。
昂贵的例子:
class Person{ public: Person(); private: std::string name; std::string address; }; class Student:public Person{ public: Student(); ~Student(); private: std::string schoolname; std::string schooladdress; }; bool validatestudent(Student s);//声明 ... Student pla; bool plaIsOk = validatestudent(pla);//调用函数 //该例子中的by value方法传递一个Student对象会导致调用一次Student copy构造函数、一次Person copy构造函数、四次string copy构造函数。当函数中Student复件被销毁,会调用六次对应的析构函数.
一般而言,可以合理假设by value不昂贵的唯一对象就是内置类型和STL的迭代器和函数对象。
以by reference方式传递参数也可以避免对象切割问题:当一个derived class对象以by value方式传递并被视为一个base class 对象,base class的copy构造函数会被调用。而“造成此对象的行为像个derived class对象”的那些特化性质全被切割掉了,仅仅留下一个base class对象。
例:
class window{ public: ... std::string name() const;//返回窗口名称 virtual void display() const;//显示窗口 }; class windowwithscroll:public window{ public: ... virtual void display() const; };
现在有一个函数:
void printNameanddisplay(window w) //参数可能被切割:参数w被构造成一个window对象。传入的wws是windowwithscroll对象。 { std::cout<<w.name();//调用的是window类型的name函数 w.display();//调用的是window类的display函数 } //当调用上述函数 windowwithscroll wws; printNameanddisplay(wws);
解决对象切割的方法:以by reference方式传递参数
void printNameanddisplay(const window& w) { std::cout<<w.name(); w.display(); }
返回reference可能出现的错误
任何时候看到一个reference,一定是指向某个既有的对象。
例:
class rational{
public:
rational(int numerator = 0 , int denumerator = 1);
...
private:
int n,d;//分子:numerator,分母:denumerator
const rational operator*(const rational& le,const rational& ri);//以by value方法返回的是一个对象
};
一个“必须返回新对象”的函数的正确写法
inline const rational operator* (const rational& le,const rational& ri) { rational rational(le.n * ri.n , le.d * ri.d); }
函数创建新对象并返回reference的错误写法:
在stack空间(local变量是在stack空间创建对象)
const rational& operator*(const rational& le,const rational& ri) { rational result(le.n * ri.n , le.d * ri.d); return result; }//local变量result会在函数退出前被销毁;
任何函数如果返回一个reference指向某个local对象,都是错误的。
在heap空间(new创建)
const rational& operator*(const rational& le,const rational& ri) { rational *result = new rational(le.n * ri.n , le.d * ri.d); return result; }//问题在于谁该对new出来的对象实施delete?
静态存储区(定义函数内部的static对象)
const rational& operator*(const rational& le,const rational& ri) { static rational result; result = ...; return result; }
bool operator==(const rational& le,const rational& ri); ... rational a,b,c,d; if((a*b)==(c*d)){}//此时(a*b)==(c*d)永远为TRUE。