Effective C++读书笔记(条款18-23)

2024-08-25 10:18

本文主要是介绍Effective C++读书笔记(条款18-23),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(四).设计与声明

____________________________________________________________________________________________________________________________________

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

#1.导入新类型可以让接口不易被误用,以函数替换对象则可以保证类型安全性。
例如:
class Date{public:Date(int month, int day, int year);...
}
//加上外覆类型:
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);...
};
struct Day{
explicit Day(int d): val(d) {}
int val;
};
struct Year{
explicit Year(int y):val(y){}
int val;
};
//则可以这样调用:
Date d(Month::Mar(), Day(30), Year(1995));
//从而保证了类型安全性和接口不易被误用。

#2.限制类型内什么事可做,什么事不可做可以预防客户错误。
例如,用const修饰operator*的“返回类型”可阻止客户因”用户自定义类型“而犯错。
if(a*b = c)...  //喔噢,原意其实是要做一次比较动作


#3."保证接口的一致性","让types行为与内置types的行为一致”可以促进接口被正确使用。


#4.任何接口要求客户必须记得做某些事情,就是有着”不正确使用“的倾向。
例如,有函数:
Investment* createInvestment();
并希望最后将Investment*指针传递给名为 getRidOfInvestment()函数,则给了
客户三次犯错误的机会:没有删除指针,删除指针多次,没有用getRidOfInvestment()函数来删除指针
我们可以将其改成如下代码:
std::tr1::shared_ptr<Investment> createInvestment()
{std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),getRidOfInvestment);retVal = ...;   //另retVal指向正确对象return retVal;
}
这样便可以防止客户犯错,避免造成资源泄漏。

#5.tr1::shared_ptr支持定制型删除器(custom deleter),这可用来防范DLL问题,可被用来
自动解除互斥锁(mutexes;见条款14)等。
____________________________________________________________________________________________________________________________________
条款19:设计class犹如设计type
#1.新type的对象应该如何被创建和消耗?
#2.对象的初始化和对象的赋值该有什么样的差别?
#3.新type如果被passed by value(以值传递),意味着什么?
#4.什么是新type的"合法值"?
#5.你的新type需要配合某个继承图系(inheritance graph)吗?
#6.你的新type需要什么样的转换?
#7.什么样的操作符和函数对此新type而言是合理的?
#8.什么样的标准函数应该被驳回?
#9.谁该取用新的type的成员?
#10.什么是type的"未声明的接口"(undeclared interface)?
#11.你的新type有多么一般化?
____________________________________________________________________________________________________________________________________
条款20:宁以pass-by-reference-to-const 替换 pass-by-value
#1.对于自定义类型,尽量以pass-by-reference-to-const 替换 pass-by-value,前
者通常比较高效,而且可以避免切割问题。
(避免构造函数和析构函数所带来的成本)

#2.对于内置类型,STL的迭代器及函数对象,pass-by-value比较适当。
(这部分符合C守则,没有构造函数带来的成本, pass-by-value通常比指针来实现
的reference更高效)

#3.对于小型自定义类型,仍应使用passed-by-reference-to-const。
理由1
对于小型拥有并不昂贵的copy构造函数,某些编译器对待”内置类型“和”用户自定义类型“
的态度截然不同,纵使两者拥有相同的底层表述。
例如:
某些编译器拒绝把只由一个double组成的对象放进缓存器内,却很乐意在一个正规基础上对
光秃秃的double这么做。
因此使用passed-by-reference-to-const传递比passed-by-value更好。
理由2:作为一个用户自定义类型,其大小容易有所变化。
理由3:改用不同的C++编译器可能改变type的大小,比如:某些标准程序库实现版本中的
string类型比其他版本大七倍。
____________________________________________________________________________________________________________________________________
条款21:必须返回对象时,别妄想返回其reference
#1.用pointer或reference指向一个local stack对象,并在函数中返回,都将导致
”无定义境地“的不明确行为,因为locak stack对象在函数返回时就被销毁了。
#2.返回pointer或reference指向一个local static而有可能需要多个这样的对象
会使多个对象同时指向一个对象,这很可能并不是一开始你所希望的。
例如:
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)==(b*d)){//当乘积相等时,做适当的相应动作;
}else{//当乘积不相等时,做适当的相应动作;
}
结果总是执行相等时的语句块,因为返回的总是同一个local static对象,
这并不是当初我们所希望的。
____________________________________________________________________________________________________________________________________
条款22:将成员变量声明为private
#1.切记将成员变量声明为private,这可赋予客户
(1).访问数据的一致性(都通过函数访问),
(2).可细微划分的访问权限(不准访问,只读访问,只写访问,读写访问),
(3).允许约束条件获得保证(通过函数验证class的约束条件,通知其他对象,
验证函数的前提和事后状态,在多线程状态中执行同步控制...等等。)
(4).增加class的封装性,及实现弹性(访问控制增加封装性;客户统一调用,
却可改变的函数内部行为增加了实现弹性)
(5).确保class的约束条件总是会获得维护,因为只有成员函数可以影响它们。

#2.某些东西的封装性与“当其内容改变时可能造成的代码破坏量”成反比。
(所谓改变,也许是从class中移除它(这或许有利于计算封装性))

#3.protected并不比public更具封装性。
理由:public成员变量供所有使用它的客户所使用,如果移除了它,所有使用
它的客户码都会被破坏,因此封装性为0, protected成员变量被所有derived
classes和class本身所使用,如果移除了它,所有使用它的客户码也都遭到
破坏,因此封装性也为0.
____________________________________________________________________________________________________________________________________
条款23:宁以 non-member, non-friend 替换 member 函数
#1.如果某些东西被封装,就会降低其可见性,愈多东西被封装,愈少人可以看见它。
而愈少人可以看见它,我们就有愈大的弹性去变化它,因为我们的改变仅仅影响那些
看到改变的人或事物。

#2.宁可拿 non-member, non-friend函数替换 member 函数。这样做可以增加
(1)封装性:
non-member、non-friend函数比member函数更缺少访问class中内部protected成员
和private成员的权限,换言之,能看到的更少,因此,封装性更好。
(注意,这里为什么不包括friend函数)

(2)包裹弹性:
non-member、non-friend函数的封装将class与该函数分离,减少了代码修改的
编译依存性,因此具有更高的包裹弹性。(说白了,就是与class更无关了)

(3)机能扩充性:
non-member、non-friend函数可以被置于与class相同的namespace内,如此一来
便可实现多组分别位于不同源文件的便利函数,从而实现了分离的扩充机能。
(namespace可以跨越多个源文件,而class不行)
例如:
//-----------------------------------
#include"WebBrowser.h"
namespace WebBrowserStuff{
class WebBrowser{...};
...        //核心机能,例如几乎所有客户所需要的 non-member函数
}
//-----------------------------------
#include"WebBrowserBookmarks.h"
namespace WebBrowserStuff{
...        //与书签相关的便利函数
}
//-----------------------------------
#include"WebBrowserCookies.h"
namespace WebBrowserStuff{
...        //与cookies相关的便利函数
}
//-----------------------------------
//此时若想再添加一组便利函数,只需要包含相关的头文件,便可以在 namespace
//中轻松的再添加,实现扩展机能。<pre name="code" class="cpp">//=============================================================
// 以下是一个使用member函数和non-member、non-friend函数的示例:
//=============================================================
class WebBrowser{
public:...void clearCache();  //清除下载元素缓存void clearHistory(); //清除访问过的URLs历史记录void removeCookies(); //移除系统中所有的Cookies
};
//若想将上述三个函数一同实现,可采用member()函数,和non-member、non-friend函数的方式,
//其中member()函数实现方式:
class WebBrowser{
public:...clearEverything();   //调用clearCache,clearHistory,removeCookies函数...
};
//non-member、non-friend函数实现方式:
void clearEverything(WebBrowser& wb)
{wb.clearCache();wb.clearHistory();wb.removeCookies();
}
 

____________________________________________________________________________________________________________________________________


这篇关于Effective C++读书笔记(条款18-23)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1105284

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给