实现
1.尽量少做转型动作
旧式转型:
C++新式转型:
2.避免返回handles指向对象的内部成分
引用、指针和迭代器统统都是所谓的handles(号码牌,用来取得某个对象),而返回一个“代表对象内部数据”的handles,随之而来的是“降低对象封装性”的风险,同时,它可能导致“虽然调用const成员函数却造成对象状态被改变”。
class Point{
public:
Point(int x,int y);
...
void setX(int newval);
void setY(int newval);
...
};
struct RectData{
Point ulhc;
Point lrhc;
};
class Rectangle{
public:
...
const Point& upperLeft() const{return pData->ulhc;} //在返回类型上加const就可以轻松去除下面将要提到的两个问题
const Point& lowerRight() const{return pData->lrhc; ...};
//调用
Point c1(0,0);
Point c2(100,100);
const Rectangle rec(c1,c2);
rec.upperLeft().setX(50);//错误,客户只能读取Point,不能改写它
1、成员变量的封装性最多只能等于“返回引用”的函数的访问级别。在本例中,public成员函数返回类中的私有成员变量,让私有成员的访问权限等同于公有的 。
2、const成员函数传出来一个引用,后者所指的数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。
所谓虚吊号码牌:常见的来源就是函数返回值,函数返回的指针或引用是函数体内部的临时变量,在函数体结束时,临时变量会被析构,最终导致指针或引用指向一个不再存在的对象。
3.为“异常安全”而努力是值得的
有异常抛出时,带有异常安全性的函数应该:
- 不泄露任何资源
- 不败坏任何数据
异常函数的三个保证:基本型、强烈性和不抛异常型
- 基本型:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。
- 强烈型:如果异常被抛出,程序状态不变。如果函数成功,就是完全成功,若果函数失败,程序会回到“调用函数之前”的状态。
- 不抛掷保证:承诺绝不抛出异常,因为他们总是能够完成他们原先承诺的功能。作用于内置类型身上的所有操作都提供不抛掷异常的保证。这是异常安全码中必不可少的关键基础材料。
强烈保证往往可以通过 copy-and-swap 技术实现
4.将文件间的编译依存关系降至最低
先看一下一个例子:
#include<string>
#include"date.h"
#include"address.h"
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
...
private:
std::string theName; //实现细目
Date theBirthDate;
Address theAddress;
};
上述三个头文件和Person之间的编译存在依赖关系,三个头文件中任何一个有所改变,所有包含Person class的文件就得重新编译,为了解决上述问题,我们将考虑handle class。
(1)采用pimpl idiom设计
pimpl为pointer to implementation,这种class内的指针名通常为pImpl。
// Person 接口
#include <string>
#include <memory>
// 前置声明
class PersonImpl; // Handle classes
class Date;
class Address;
class Person {
public:
Person(const std::string& name, const Date& birthday, const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
//...
private:
// pimpl idiom(pointer to implementation)
std::tr1::shared_ptr<PersonImpl> pImpl; // 指针,指向实现物,shared_ptr 在memory头文件
};
// Person 的Handle class,实现Person class中的接口
#include "Person.h" // Person的定义式
// include PersonImpl的class定义式,用于调用其成员函数,
// 注意PersonImpl有着和Person完全相同的成员函数。两者接口完全相同
#include "PersonImpl.h"
Person::Person(const std::string& name, const Date& birthday, const Address& addr)
:pImpl(new PersonImpl(name, birthday, addr))
{}
std::string Person::name() const
{
return pImpl->name(); //让Person变成一个Handle class 并不会改变它做的事,智慧改变它做事的方法
}
(2)采用 Interface class,令Person class 为 abstract base class
class Person {
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
...
};
对于 Interface class用户通常通过“factory函数或virtual构造函数”实现具体化,该函数往往被声明为static:
class Person {
...
static std::tr1::shared_ptr<Person>
create(const std::string& name, const Date& birthday, const Address& addr);
};
Derived class:
class RealPerson: public Person {
public:
RealPerson(const std::string& name, const Date& birthday, const Address& addr)
:theName(name), theBirthDate(birthday), theAddress(addr)
{}
virtual ~RealPerson() {}
std::string name() const;
std::string birthDate() const;
std::string address() const;
//...
private:
/*实现条目*/
std::string theName;
Date theBirthDate;
Address theAddress;
};
std::tr1::shared_ptr<Person> Person::create(const std::string& name,
const Date& birthday,
const Address& addr)
{
return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday, addr));
}
编译依赖性最小化的本质:
- 如果使用object reference或object pointers可以完成任务,就不要使用objects。
- 如果能够,尽量以class声明式替换class定义式。
- 为声明式和定义式提供不同的头文件。