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++》总结篇(设计与声明)

条款十八:让接口容易被使用,不易被误用

1.我们应该预防“接口被误用”。
假设有这么一个构造函数

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int month, int day, int year) {}
};
int main()
{
	Data d1(30, 3, 1995);//正确
	Data d2(1995, 30, 3);//无效的年月日!
	return 0;
}

为了防止他们被误用,所以我们最好给年月日都分别导入一个外覆类型。
以及,限制月份只有12个月,可以预先定义所有有效月份。(不用枚举是因为枚举可以转int)
#include <iostream>
using namespace std;
struct day
{
	day(int x) :val(x) {}
	int val;
};
struct month
{
	static month Jan() { return month(1); }
	static month Feb() { return month(2); }
	int val() { return _val; }
private:
	int _val;
	month(int x) :_val(x) {}
};
struct year
{
	year(int x) :val(x) {}
	int val;
};
class Data
{
public:
	Data(month month, day day, year year) {}
};
int main()
{
	Data d1(month::Feb(), 3, 1995);//正确
	Data d2(month::Jan(), 30, 3);//无效的年月日!
	return 0;
}

2.避免无端与内置类型不兼容,提供行为一致性的接口
stl容器的接口就十分的一致性,比如每个容器都有size成员函数,表示容器内有多少对象。
而像java或者.net则有Length和Count使得开发人员混淆。

3.不要让接口要求客户必须记得做某事。
因为客户可能会忘记做那件事,导致不可预料的结果
如一个函数动态分配对象并返回对象的指针给用户,如: A* createA()
意味着A必须被用户delete,而且很有可能客户删除A超过一次。这就存在两个错误的机会了!

所以我们应该用智能指针去管理它,最好直接返回智能指针给客户,这样直接彻底消除忘记删除对象的可能性。而不是让客户去管理它,消除客户的资产管理责任。

4.”corss-DLL problem”问题
即,对象在动态链接库DLL中被new创建,却在另一个DLL销毁。会导致运行期错误。
我们可以定制shared_ptr的删除器,他会追踪记录,当对象引用计数为0的时候调用那个DLL的delete,而不是另一个DLL的delete

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

条款十九:设计class犹如设计type

当你定义一个新的class,就犹如定义了一个新的type。这是一个艰巨的任务,那么如何设计呢?

(1) 新type的对象应该如何被创建和销毁?
这会影响到你的class的构造函数和析构函数以及内存分配函数和释放函数(operator new ,operator new[],operator delete和operator delete[]–见第8章)的设计,当然前提是如果你打算撰写它们。

(2)对象的初始化和对象的赋值该有什么样的差别?
这个答案决定你的构造函数和赋值(assignment)操作符的行为,以及其间的差异。很重要的是别混淆了“初始化”和“赋值”,因为它们对应于不同的函数调用。

(3)新type的对象如果被passed by value(以值传递),意味着什么?
记住,copy构造函数用来定义一个type的pass-by-value该如何实现。

(4)什么是新type的“合法值”?
对class的成员变量而言,通常只有某些数值集是有效的。那些数值集决定你的class必须维护的约束条件(invariants),也就决定了你的成员函数(特别是构造函数、赋值操作符合所谓“setter”函数)必须进行的错误检查工作。它也影响函数抛出的异常、以及(极少被使用的)函数异常明细列(exception specifications)。

(5)你的新type需要配合某个继承图系(inheritance graph)吗?
如果你继承自某些既有的classes,你就受到那些classes的设计的束缚,特别是收到“它们的函数是virtual 或non-virtual”的影响(见条款34和条款36)。如果你允许其他classes继承你的class,那会影响你所声明的函数-尤其是析构函数-是否为virtual(见条款7)。

(6)你的新type需要什么样的转换?
你的type生存与其他types之间,因而彼此该有转换行为吗?如果你希望允许类型T1之物被隐式转换为类型T2之物,就必须在class T1内写一个类型转换函数(operator T2)或在class T2内写一个non-explicit-one-argument(可被单一实参调用)的构造函数。如果你只允许explicit构造函数存在,就得写出专门负责执行转换的函数,且不得为类型转换操作符(type conversion operators)或non-explicit-one-argument构造函数。(条款15有隐式和显示转换函数的范例。)

(7)什么样的操作符和函数对此新type而言是合理的?
这个问题的答案决定将为你的class声明哪些函数。其中某些该是member函数,某些则否(见条款23,24,46)。

(8)什么样的标准函数应该驳回?
那些正是你必须声明为private者(见条款6)。

(9)谁该取用新type的成员?
这个提问可以帮助你决定哪个成员为public,哪个成员为protected,哪个为private.它也帮助你决定哪一个classes 和/或 functions应该是friends,以及将它们嵌套于另一个之内是否合理。

(10)什么是新type的“未声明接口”(undeclared interface)?
它对效率、异常安全性(见条款29)以及资源运用(例如多任务锁定和动态内存)提供何种保证?
你在这些方面提供的保证将为你的class实现代码加上相应的约束条件。

(11)你的新type有多么一般化?
或许你其实并非定义一个新type,而是定义一整个types家族。果真如此你就不应该定义一个新class,而是应该定义一个新的class template.

(12)你真的需要一个新type吗?
如果只是定义新的derived class以便为既有的class添加机能,那么说不定单纯定义一或多个non-member函数或templates,更能够达到目标。

这些问题不容易回答,所以定义出高效的classes是一种挑战。然后如果能够设计至少像C++内置类型一样好的用户自定义(user-defined)classes,一切函数便都值得。

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

条款二十:宁以pass-by-refernce-to-const替换pass-by-value

1.宁以pass-by-refernce-to-const替换pass-by-value
因为传值会产生昂贵的拷贝构造操作和析构操作。而且如果对象内有其他对象,或者继承自其他对象,会造成更大的开销!
但如果是pass by refernce to const就能回避所有构造和析构操作,这种传递方式效率特别高,因为没有任何对象的创建,而且可以避免切割问题
切割问题:当一个继承对象by value方式传递并视为一个base class对象,那么dervied对象的特化性质全被切割掉了,只留下base对象。因为正是base class对象构造了它。

2.此规则不适用于内置类型
references实现原理是指针,所以你的对象属于内置类型(int),那么pass by value会比pass by reference效率高。
此规则也同样适用于stl,因为习惯上它们都被设计为passed by value。

请记住:
尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高校。并可解决切个问题。
以上规则并不适用于内置类型,以及stl的迭代器和函数对象。对它们用pass-by-value比较妥当。

条款二十一:必须返回对象时,别妄想返回其reference

别无脑的返回reference,
看以下代码:

#include <iostream>
using namespace std;
class A
{
public:
	A(int x) :value(x) {}
	int value;

	const A& operator*(const A& rhs)
	{
		A res(value * rhs.value);
		return res;
//很糟糕
	}
};
int main()
{
	A a(2);
	A b(3);
	A c = a * b;
	return 0;
}

此时operator*返回的是local对象,那么在函数退出前就被销毁了。

那如果改用A*呢?

friend A& operator*(const A& lhs,const A& rhs)
{
	int _x = (lhs.value) * (rhs.value);
	A* res=new A(_x);
	return *res;
}

那这样就会面临没有delete的情况,
尤其是当你w=xyz的时候,你调用了两次operator*意味着new了两次,你不得不delete两次,但是你无法取得引用背后隐藏的指针,最后造成内存泄漏。

正确写法应该是:

friend A operator*(const A& lhs,const A& rhs)
{
	int _x = (lhs.value) * (rhs.value);
	A res(_x);
	return res;
}

但这样会承受构造和析构成本。

总结:当你必须在”返回一个refernce”和”返回一个object“之间抉择时,你的工作就是挑出行为正确的那个。

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

条款二十二:将成员变量声明为private

类似C#的属性一样,C++可以通过某些手段达到和C#属性一样的效果,只读或者只写,或者可读可写。
将成员变量设置为private为的是封装性,而且这样外界只能靠成员函数影响它们。
protected其实很打咩,因为当我们取消了某个protected成员变量,所有的derived classes都会被破坏,这和public其实如出一辙,所以不要觉得protected的封装性高过public。
从封装的角度看,访问权限只有private和其他。

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

条款二十三:宁以non-member,non-friend替换menber函数

这一篇有点绕,我也没怎么看懂,大概就是尽可能使用非成员函数调用成员来增加封装性,包裹弹性和机能扩充性。

#include <iostream>
using namespace std;
namespace Aclass
{
	class A
	{
	public:
		void clear1() {}
		void clear2() {}
		void remove() {}

		void clearAll() { clear1(); clear2(); remove(); }
	};
	void ClearA(A& a) { a.clear1(); a.clear2(); a.clearAll(); }
}
int main()
{
	Aclass::A a;
	a.clearAll();//这个不好
	Aclass::ClearA(a);//这个更好
	return 0;
}

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

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

看下面这个代码:

#include <iostream>
using namespace std;

class A
{
public:
	A(int x) :value(x) {}
	int value;
	const A operator*(const A& rhs)const
	{
		return A(value * rhs.value);
	}
};

int main()
{
	A a(10);
	A b = a * 10;//可以
	A c = 10 * a;//不行
	return 0;
}

为什么A b = a * 10;可以
而A c = 10 * a;不行
因为a*10的10做了隐式类型转换
实际上是A b1 = a * (const A(10));

但这可不好,乘法不满足交换律??
所以最佳改进方案是用非成员函数:

#include <iostream>
using namespace std;

class A
{
public:
	A(int x) :value(x) {}
	int value;
};
const A operator*(const A& lhs, const A& rhs)
{
	return A(lhs.value * rhs.value);
}
int main()
{
	A a(10);
	A b = a * 10;//可以
	A c = 10 * a;//可以
	return 0;
}

至于为什么不用友元函数?不知道,这是一个值得争议的话题,书上是认为不该用的。

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

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

本人理解浅薄,未看懂书上内容,所以找了一篇高质量文章代替

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-24 17:55:54  更:2022-05-24 17:57:04 
 
开发: 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年5日历 -2024/5/14 2:17:13-

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