C++ 改善程序与设计具体做法

命名习惯

  • lhs、rhs分别作为左值与右值
  • pT表示指向一个T型对象的指针
  • rT表示T型对象的一个引用
  • doSomething作为胡写的函数
  • 使用Stuff后缀、the前缀、func后缀
  • 类名与名称空间的首字母大写,变量与函数名的首字母小写
  • other、temp、msg、ret、ans等变量名
class AccessLevels{
public:
	int getReadOnly();
	void doSomething();
private:
	int readOnly;	
}

尽量以const,enum,inline替换#define

尽量替换#define PI 3.14为:const double PI = 3.14;

对于类的专有成员, 使用static或者enum:

class GamePlayer {
private:
	static const int NumTurns = 5;
	int scores[NumTurns];
}
const int GamePlayer::NumTurns; //只声明不定义
class GamePlayer {
private:
	enum { NumTurns = 5 };
	int scores[NumTurns];
}

以传常引用代替传值

函数调用时,要创建单独的实参,传值就伴随着多个构造与析构操作。
另一方面,引用的汇编底层是常量指针,传引用的代价很小。

不要返回临时变量的引用

函数调用过程中的临时变量,在函数执行完毕后会析构,不应返回其指针。一个例外是返回函数static对象的引用。

提供const与non-const成员函数并使用类型转换重写

const与non-const成员函数的匹配是函数重载。常量对象优先调用const成员函数。

class TextBlock {
public:
	const char& operator[](size_t position) const
	{
		return text[position];
	}
	char& operator[](size_t position)	
	{
		//先转型为const对象调用const成员函数,再对返回值类型做修改
		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
	}
private:
	string text;
};

使用列表初始化和类内初始值

使用未被初始化的对象会导致不明确的行为,要确保对象被使用前已被初始化。
构造函数先执行初始化操作,后执行构造函数体,使用列表初始化可以减少一次默认构造函数的调用。

class ABEntry {
public:
	ABEntry( ): theName(), thePhones();
private:
	string theName;
	list<PhoneNumber> thePhones;
	int numTimesConsulted = 0;
};

使用工厂函数

工厂函数是一个用来创建对象的函数,它像工厂一样,“生产”出来的函数都是“标准件”(拥有同样的属性)。

其一般由两部分组成:创建一个对象,返回引用或指针
工厂函数返回智能指针较为合适。

使用local static对象替换non-local static对象

类设计者提供的接口往往包含一个具体的对象实例,由用户进行使用。如果用户编译时,对该对象的使用早于该对象的创建,那么就会出现未定义的行为。

如果工厂函数创建的是一个static对象,即可解决这一问题,称为Singleton(独生子女)模式。

合理利用编译器自动生成的构造函数

编译器自动合成default构造函数、copy构造函数、copy赋值运算符重载,以及析构函数。(当类中内含const 或 引用时,不生成copy)

默认构造函数的作用是对每个成员调用他自己的默认构造函数。
copy的作用是对每一个成员进行赋值操作。
析构函数的作用是对每个成员调用它自己的析构函数。

可使用 =default 保留编译器自动合成的函数,使用 =delete要求编译器不自动合成。

明确类的用途

有些类被设计成多态基类,这类函数包含许多virtual成员函数,且一定使用virtual析构函数。
有些类单纯被设计为继承基类,不需要virtual析构函数。
有些类压根不被设计为基类,如string、vector等。

virtual函数带来了虚函数表的空间开销和运行时动态联编的时间开销。

不在构造和析构函数过程中调用virtual函数

因为构造顺序为基类 -> 派生类,析构顺序为派生类 -> 基类,在构造和析构函数中调用virtual函数运行的是基类的函数。

注意派生类的拷贝构造和拷贝运算符

处理派生类的copying函数时,一定要单独处理基类部分。

class Base { };
class Derived : public Base{
public:
	Derived(const Derived& d) : Base(d), name(d.name) {}
	Derived& operaotr=(const Derived& rhs)
	{
		Base::operator=(rhs);
		name = d.name;
	}

private:
	string name;
};

inline

  1. inline函数是一个函数,但它又不会带来函数调用带来的开销。

  2. 但它也会增加目标码的体积,inline带来的代码膨胀在运行过程中亦会导致额外的换页行为。此外,inline函数无法设置断点,无法进行调试。

  3. inlining是编译期间的行为,通常把inline函数写在头文件中(因为在编译时要看见函数的原型)

  4. inline 对编译器而言只是建议,编译器可以拒绝把函数inlining。

  5. 构造与析构函数尤其不适合作为inline函数。因为C++对“对象被创建和被销毁时发生什么事”做了各种各样的保证。实际的构造析构函数的体积比我们写的体积大得多。

  6. 较为合理的策略是将inlining限制在小型、被调用频繁的函数身上。

copy- and - swap

swap是一个有趣的函数。原本只是STL的一部分,而后成为异常安全性编程的中流砥柱,也可用于处理自我赋值。

拷贝并复制往往能提供较好的异常安全性,其基本思想是创建一个实际需要的副本,对副本做修改,最后将原对象与副本做交换。

std::swap的典型实现,包含三个赋值操作:

namespace std{
	template<typename T>
	void swap(T& a, T& b)
	{
		T temp = a;
		a = b;
		b = temp;
	}
}

std::swap有时不能满足我们的需求,考虑下面的Example类。

class ExampleImpl {
private:
	vector<string> G;
};
class Example{
public:
	Example& operator=(const Example& rhs)
	{
		*pImpl = *(rhs.pImpl);	//pImpl执行深拷贝
	}
private:
	ExampleImpl* pImpl;
};

如果对两个Example对象进行std::swap调用,那么会进行两个ExampleImpl对象的深拷贝,非常耗时。
但实际上,我们只需要交换两个pImpl指针即可。

void Example::swap(Example& other)
{
	std::swap(pImpl, other.pImpl);
}

想要进行swap时,执行成员函数版本的swap

处理自我赋值

处理自我复制的难题主要出现在深拷贝中,假设Example类含有一个string* p。
以下为一种异常安全码。

Example& Example::operator=(const Example& rhs)
{
	string* pnew = new(*(rhs.p));
	delete p;
	p = pnew;
	return *this;
}

另一种方法是使用copy - and - swap 技术

Example& Example::operator=(const Example& rhs)
{
	Example temp(rhs);
	swap(*this, temp);
	return *this;
}

资源管理

详见我的另一篇博客:C++ 资源管理 shared_ptr

让接口容易被使用,不容易被误用

详见我的另一篇博客:封装与接口设计 C++类与对象

降低编译依存度,减少接口与实现之间的耦合

详见我的另一篇博客:C++接口与实现分离 降低编译依存度的两种做法代码

面向对象设计

详见我的另一篇博客:继承与面向对象设计 virtual的替换方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值