高效C++编程学习笔记(四)

设计与声明

条款20. 用引用代替值传递

在不改变函数传递对象的前提下,函数参数尽量使用引用,总所周知的功能主要有以下两点

  1. 能提高传参效率,避免多次拷贝与析构。
  2. 避免对象切割。
    以代码为例。
class Window{
	public:
		...
		std::string name() const;	//返回窗口名称
		virtual void display() const;	//显示窗口和其内容
};
class WindowWithScrollBars:public Window{
	public:
		...
		virtual void display()const;
};

当发生如下调用时:

void printNameAndDisplay(Window w)		
{
	std::cout << w.name();
	w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);			//照成对象切割,只留下基类的特性

而对于内置对象(例如int)还有STL中的迭代器与函数对象使用值传递比较合适。

条款21.返回对象时,别返回引用(reference)

  1. 由于返回的函数内部local stack对象,在引用钱被销毁。
  2. 返回reference指向一个heap-allocated对象,会存在内存泄漏的问题。
  3. 返回reference指向一个local static时可能需要多个这样的对象

条款22.将成员变量声明为private

以及对成员变量的可控性都要求将变量声明为private。C++三大特性的封装为以后替换
不同的实现方式提供了可能。
protected并不比public更加具有封装性。

条款23.用non-member,non-friend替换member函数

对于不直接涉及class私有成员的函数尽量使用non-member与,non-friend函数替代。可以导致较低的编译相依度与增加class的可延伸性。以一个用于表示网页浏览器的class为例。在其众多的函数中有一些用来清除下载高速缓存区、清除访问过的URLs的历史记录,以及移除系统中的所有cookies:

class WebBrower{
	public:
	...
	void clearCache();
	void clearHistory();
	void removeCookies();
	...
	void clearEverything();	//调用上面三个清除函数
};

也可以使用non-member函数替换上面clearEverying函数

void clearBrowser(WebBrowser& wb)
{
	wb.clearCache();
	wb.clearHistory();
	wb.removeCookies();
}

由于non-member函数并不能增加“能够访问class内之private成分”,提供了较大的封装性。

一种较为常见的做法为将clearBrowser成为一个non-member函数位于WebBrowser所在的一个namespace(命名空间)内:

namespace WebBrowserStuff{
	class WebBrowser{...};
	void clearBrowser(WebBrowser& wb);
	...
}

对于一个WebBrowser这样的class可以拥有大量便利函数,将各个功能分类声明与一个头文件中

//头文件WebBrowser.h——这个头文件针对class WebBrowser自身
//及WebBrowser核心机能
namespace WebBrowserStufff{
class WebBrowser{...};
	...	//核心机能。所有客户都需要
		//non-member函数
}
//头文件“webbrowserbookmarks.h”
namespace WebBrowserStuff{
	...		//与书签相关的便利函数
}
//头文件“webbrowsercookies.h”
namespace WebBrowserStuff{
	...	//与cookie相关的便利函数
}

这种方式类似于C++标准库,可以轻松扩展命名空间中的便利函数。

条款24.若所有参数需要类型转换,请为此采用non-member函数。

对于隐式类型转换的class,建立数值型类时,计算时常常需要隐式转换。

class Rational{
public:
	Rational(int numberator = 0,int denominator = 1);	//允许隐式转换
	int numberator() const;
	int denominator() const;
	const Ration operator* (const Rational& rhs) const;
private:
	...
};

普通的类之间的计算没问题,但是混合计算时会出现问题

Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result = oneHalf * oneEighth;	//OK
result = result * oneEighth;						//OK
result = oneHalf * 2;		//2通过隐式转换成 Rational temp(2),OK
result = 2 * oneHalf ;		//error

为了支持混合运算可以将operator* 成为一个non-member函数。

class Rational{
	...							//不包含operator*
};

const Rational operator*(cosnt Rational& lhs,const Rational& rhs)
{
	return Ration(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
}
Rational oneFourth(1,4);
Rational result ;
result = 2 * oneFourth ;	//ok
  • 如果要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必定是个non-member。

条款25.写出一个不抛出异常的swap函数

缺省下的swap动作由标准程序库swap算法完成

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

缺省的swap通过重复copy构造函数和copy assignment操作符完成,效率极低。
对于那种pimpl(pointer to implementation)"以指针指向一个对象,内含真正数据"的那种类型。使用缺省的swap会很大的降低效率。
举个例子

class WidgetImpl{
	public:
		...
	private:
		int a,b,c;
		std::vector<doublle> v;
		...
};
class Widget{
	public:
		Widget(const Widget& rhs);
		Widget& operator=(const Widget& rhs)	//赋值Widget时,令它赋值其WidgetImpl对象
		{
			...
			*pImpl = * (rhs.pImpl);
			...
		}
private:
	WidgetImpl* pImpl;			//指针,所指对象内含Widget数据
};

可以如下改写,与STL容器具有一致性

class Widget{
public:
...
void swap(Widget& other)		//Widget成员函数胡
{
	using std::swap;
	swap(pImpl,other.pImpl);			
	...
}
};
namespace std{
templat<>		//模板类全特化,std::swap针对“T是Widget”的特化版本
void swap<Widget>(Widget& a,Widget& b)
{
	a.swap(b);			//置换Widgets,调用其swap成员函数
}
};

另外一种情况,Widget与WidgetImpl都是class templates而非classes,在Widget内(以及WidgetImpl内)放个swap成员函数和上述一样。但是注意特化std::swap时,由于模板函数不支持偏特化(partially specialize)所以不能如之前那样

namespace std{
	template<typename T>
	void swap< Widget<T>>(Widget<T>&a,Widget<T>&b)	//错误!模板函数不支持偏特化
	{ a.swap(b);}
}

当然可以如下操作,为打算偏特化的函数模板添加一个重载版本。

namespace std{
	template<typename T>
	void swap(Widget<T>& a,Widget<T>& b)	//(注意swap之后没跟<...>)
	{a.swap(b);}
}

但是这样子违反了STL的标准。
一种较为简单的方式是声明一个non-member swap,让它调用member swap,将所有相关机能都被置于命名空间WidgetStuff内。

namespace WidgetStuff{
	...										//模板化的WidgetImpl等等
	template<typename T>	//同前,内含swap成员函数
	class Widget{...};				
	...
	template<typename T>	//non-member swap函数
	void swap(Widget<T>&a,Widget<T>&b)		//这里并不属于std命名空间。
	{ a.swap(b); }
}

如何确保每次能够最佳调用,在专属版本不存在的情况下调用std内的一般化版本

template<ytpename T>
void doSomething(T& obj1,T& obj2)
{
	using std::swap;	//令std::swap在此函数内可用
	...
	swap(obj1,obj2);	//为T型对象调用最佳swap版本
	...
}

C++名称查找法则(name lookup rules)确保将找到global作用域或T所在之命名空间内的任何T专属的swap。如果T是Widget并位于命名空间WidgetStuff内,编译器会使用“实参取决之查找规则”(argument-dependent lookup)找出WidgetStuf内的swap。如果没有T专属的swap存在,编译器就使用std内的swap。

且不需要为调用添加额外的修饰符

std::swap(obj1,obj2);		//这是错误的swap调用方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值