IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Effective C++ 笔记 -> 正文阅读

[C++知识库]Effective C++ 笔记

4 设计与声明


条款 18 :让接口容易被正确使用,不易被误用

// 设计一个日期的 class 构造函数
class Date
{
public:
	Date(int month, int day, int year);
	~Date();
	...
};

// 错误使用示例1
Date d(30, 3, 1995); // 应该是 3,30 而不是 30,3
// 错误使用示例2
Date d(2, 30, 1995); // 应该是 3,30 而不是 2,30

// 使用外覆类型来区别天数、月份和年份
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 Date
{
public:
	Date(const Month& m, const Day& d, const Year& y);
	~Date();
	...
};
Date d(30, 3, 1995);	// 错误!不正确的类型
Date d(Day(30), Month(3), Year(1995));	// 错误!不正确的类型
Date d(Month(3), Day(30), Year(1995));	// ok,类型正确

// 令 Day, Month 和 Year 成为成熟且经充分锻炼的 classes 并封装其内部数据,比简单使用上述的structs 好
class Month
{
public:
	static Month Jan() { return Month(1); }
	static Month Feb() { return Month(2); }
	...
	static Month Dec() { return Month(12); }
	...
private:
	explicit Month(int m);	// 阻止生成新的月份
	...						// 这是月份专属数据
	
};
Date d(Month::Mar(), Day(30), Year(1995));

请记住:

  • 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
  • “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
  • “阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
  • tr1::shared_ptr 支持定制型删除器。这可防范DLL(动态链接库程序)问题,可被用来自动解除互斥锁等等。?

?条款 19 :设计 class 犹如设计 type

如何设计高效的classes呢?首先你必须了解你面对的问题。几乎每一个class都要求你面对以下提问,而你的回答往往导致你的设计规范:

  1. 新 type 的对象应该如何被创建和销毁?
  2. 对象的初始化和对象的赋值该有什么样的差别?
  3. 新 type 的对象如果被 passed by value (以值传递),意味着什么?
  4. 什么是新 type 的“合法值”?
  5. 你的新 type 需要配合某个继承图系(inheritance graph)吗?
  6. 你的新 type 需要什么样的转换?
  7. 什么样的操作符和函数对此新 type 而言是合理的?
  8. 什么样的标准函数应该驳回?
  9. 谁该取用新 type 的成员?
  10. 什么是新 type 的“未声明接口”?
  11. 你的新 type 有多么一般化?
  12. 你真的需要一个新 type 吗?

请记住:

  • class 的设计就是 type 的设计。在定义一个新的 type 之前,请确定你已经考虑过本条款覆盖的所有讨论主题。

条款 20 :宁以 pass-by-reference-to-const 替换 pass-by-value?

class Person
{
public:
	Person();
	virtual ~Person();
	...
private:
	std::string name;
	std::string address;
};
class Student: public Person
{
public:
	Student();
	~Student();
	...
private:
	std::string schoolName;
	std::string schoolAddress;
};
// 以 by value 方式定义形参
bool validateStudent(Student s);
Student plato;
bool platoIsOk = validateStudent(plato);

// 以 pass by reference to const 方式
bool validateStudent(const Student& s)

// 好处:1. 回避多余的构造和析构动作;
//		2. 避免slicing(对象切割)问题
// 示例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);	// 会造成派生类的特化信息被切除

// 正确范例
void printNameAndDisplay(const Window& w)
{
	std::cout << w.name();
	w.display();
}

请记住:

  • 尽量以 pass by reference to const 替换 pass by value. 前者通常比较高效,并可避免切割问题。
  • 以上规则并不适用与内置类型,以及 STL 的迭代器和函数对象。对它们而言,pass by value 往往比较适当。

条款 21 :必须返回对象时,别妄想返回其 reference?

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1);
	...
private:
	int n, d;		// n (numerator):分子,m(denominator):分母
	friend const Rational operator*(
		const Rational& lhs, const Rational& rhs);
};

// 不正确示范1
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	Rational result(lhs.n * rhs.n, lhs.d * rhs.d);	// 警告!糟糕的代码!
	return result;
}
// 不正确示范2
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);	// 警告!更遭的写法
	return *result;
}
// 不正确示范3
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
	static Rational result;
	result = ...;
	return result;
}

bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
	...
} else {
	...
}
// 不可!因为表达式((a * b) == (c * d))总是被核算为true, 不论 a, b, c, d的值是什么!
// 一旦将代码重新写未等价的函数形式,很容易就可以了解出了什么意外:
if (operator==(operator*(a, b), operator*(c, d)))
{
	/* code */
} // 虽然都对 static Rattional 对象值做了改变,但是因为都是 reference, 所以调用端看到的永远是该对象的“现值”

// 正确做法是:就让那个函数返回一个新对象
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

请记住:

  • 绝不要返回 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static 对象而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回 reference 指向一个 local static 对象”提供了一份设计实例。

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

// 使用函数可以让你对成员变量的处理有更精确的控制
class AccessLevels
{
public:
	...
	int getReadOnly() const { return readOnly; }
	void setReadWrite(int value) { readWrite = value; }
	int getReadWrite() const { return readWrite; }
	void setWriteOnly(int value) { writeOnly = value; }
private:
	int onAccess;
	int readOnly;
	int readWrite;
	int writeOnly;
};

// 封装性
/*
* protected 成员变量的封装性是不是高过public成员变量?答案令人惊讶:并非如此。
* 假设我们有一个 protected 成员变量,而我们最终取消了它,有多少代码被破坏?唔,所有使用它的
* derived classes 都会被破坏,那往往是个不可知的大量。 因此,protected 成员变量就像public
* 成员变量一样缺乏封装性。
*/
// 从封装的角度观之,其实只有两种访问权限:private(提供封装)和其他(不提供封装)。

请记住:

  • 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
  • protected 并不比 public 更具封装性。

条款 23 :宁以non-member、non-friend 替换 member 函数?

class WebBrowsesr
{
public:
	...
	void clearCache();
	void clearHistory();
	void removeCookies();
	...
};

// 许多用户会想一整个执行所有动作,因此 WebBrowsesr 也提供这样一个函数
class WebBrowser
{
public:
	...
	void clearEverything();
	...
};
// 当然,这一机能也可以由一个 non-member 函数调用适当的 member 函数而提供出来
void clearBrowser(WebBrowsesr& wb)
{
	wb.clearCache();
	wb.clearHistory();
	wb.removeCookies();
}
// 那么,哪一个比较好呢?
/*
* 很显然,是后者。原因在于 non-memeber non-friend 函数不会增加能够访问class内之private
* 成分的函数数量。
*/

// c++ 中比较自然做法是让 clearBrowser 成为一个 non-member 函数并且位于 WebBrowser 所在的
// 同一个 namespace(命名空间)内:
namespace WebBrowserStuff {
	class WebBrowser
	{
	public:
		WebBrowser();
		~WebBrowser();
		...
	};
	void clearBrowser(WebBrowser& wb);
	...
}

// 头文件 “webbrowser.h” —— 这个头文件针对 class WebBrowser 自身及 WebBrowser 核心机能
namespace WebBrowserStuff {
	class WebBrowser
	{
	public:
		WebBrowser();
		~WebBrowser();
		...
	};
	...	// 核心机能,例如几乎所有客户都需要的 non-member 函数
}

// 头文件“webbrowserbookmarks.h”
namespace WebBrowserStuff {
	... // 与书签相关的便利函数
}

// 头文件“webbrowsercookies.h”
namespace WebBrowserStuff {
	... // 与 cookie 相关的便利函数
}
// 以此种方式切割机能并不适用于 class 成员函数,因为一个 class 必须整体定义,不能被分割为片片段段

请记住:

  • 宁可拿 non-member non-friend 函数替换 member 函数。这样做可以增加封装性、包裹弹性和机能扩充性。

条款 24 :若所有参数皆需类型转换,请为此采用 non-member 函数?

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1);
	int numerator() const;
	int denominator() const;
private:
	...
};

// 示例1 假设 operator* 写成 Rational 成员函数
class Rational
{
public:
	...
	const Rational operator*(const Rational& rhs) const;
};
Rational oneHalf(1, 2);
Rational result;
result = oneHalf * 2; 	// ok
result = 2 * oneHalf;	// error

// 示例2 让 operator* 成为一个 non-member 函数 
class Rational
{
public:
	...					// 不包括 operator*
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

请记住:

  • 如果你需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是一个 non-member.

条款 25 :考虑写出一个不抛出异常的 swap 函数

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

// pimple 手法:pointer to implementation,以指针指向一个对象,内含真正数据
class WidgetImpl
{
public:
	...
private:
	int a, b, c;
	std::vector<double> v;
	...
};

class Widget
{
public:
	Widget(const Widget& rhs);
	Widget& operator=(const Widget& rhs)
	{
		...
		*pImpl = *(rhs.pImpl);
		...
	}
private:
	WidgetImpl* pImpl;	
};

// 1. 提供一个成员 swap 函数
class Widget
{
public:
	...
	void swap(Widget& other)
	{
		using std::swap;
		swap(pImpl, other.pImpl);
	}
	...
};

// 2. 特化版 swap函数,调用成员 swap 函数
namespace std {
	template<>
	void swap<Widget>(Widget& a, Widget& b)
	{
		a.swap(b);
	}
}

// 假设 上述类都为类模板
template<typename T>
class WidgetImpl
{
public:
	WidgetImpl();
	~WidgetImpl();
	...
};

template<typename T>
class Widget
{
public:
	Widget();
	~Widget();
	...
};

// 此时可将其类声明和 non-member swap 函数置于 WidgetStuff 内
namespace WidgetStuff {
	...					// 模板化的 WidgetImpl 等等
	template<typename T>
	class Widget { ... };	// 同前,内含 swap 成员函数
	...
	template<typename T>
	void swap(Widget<T>& a, Widget<T>& b)
	{
		a.swap(b);		// non-member 函数
	}
}

请记住:

  • 当 std:: swap 对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
  • 如果你提供一个 member swap,也该提供一个non-member swap 用来调用前者。对于classes(而非 templates),也请特化 std::swap.
  • 调用swap时应针对 std::swap 使用 using 声明式,然后调用swap并且不带任何“命令空间资格修饰”。
  • 为“用户定义类型”进行std templates 全特化是好的,但千万不要尝试在std 内加入某些对std 而言全新的东西。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-18 11:01:39  更:2021-11-18 11:04:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:54:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码