Effective C++ 摘记(一)

2024-01-05 08:48
文章标签 c++ effective 摘记

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

(一)、让自己习惯C++

一、C++语言联邦

多重范型编程语言:过程式、面向对象式、函数式编程、泛型编程、模板元编程。

二、constenuminline替换#define

const:代替宏变量有助于编译器理解;可以实现类内的常量定义(宏定义对全局有效,不具有封装性)

enumenum hack,更像define,不消耗内存,无法取地址;


inline:宏函数尽量用inline代替。


三、const

如果const出现在 *左边,则表示数据是常量;如果const出现在 * 右边,则表示指针是常量;如果* 左边和右边都有const,则表示数据和指针都是常量。



const返回值:避免(a*b)=c的错误;

const func(); //函数的返回值不可被改变

const参数:传递指向常量的引用;

const成员函数:允许const属性的重载。

在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于改变数据成员的成员函数不能加 const。所以 const 关键字对成员函数的行为作了更加明确的限定:有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内),只能读取数据成员,不能改变数据成员;没有 const 修饰的成员函数,对数据成员则是可读可写的。

除此之外,在类的成员函数后面加 const 还有什么好处呢?那就是常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函数。正如非const类型的数据可以给const类型的变量赋值一样,反之则不成立。

const修饰的函数可以被non-const对象调用,而 非const修饰的函数不可以被 const 对象调用。


如果函数返回值是个引用,则改变该值有意义;而如果函数返回值是个 内置数据值,则改变该值无意义,或者不合法。
char& operator[] (int pos) ;改变返回值 char& 合法。

char operator[] (int pos) ;改变返回值 char 无意义(甚至不合法)。


四、对象使用前初始化

构造函数成员初始化列表;

c++规定,对象成员的初始化发生在进入构造函数本体之前。



    static变量,其寿命从被构造出来直到程序结束为止,因此stack和heap-based对象都被排除。这种对象包括global对象、定义于namespace作用域内的对象,classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static对象(因为它们对函数而言是local),其他static对象成为non-local static对象,在程序结束时static对象会被自动销毁,也就是他们的析构函数会在main()结束时被调用

local-static变量是在函数内部定义的static变量,局部静态变量和全局静态变量都存储在静态内存中,所以函数退出后局部静态变量不会被释放;不同于全局静态变量的是,它不是file scope visible的,也就是说在file scope 范围内是不能访问的,只能是再次调用该函数时该变量可见(注意static变量只初始化一次)

c++对于定义在不同编译单元内的non-local-static变量的初始化没有明确定义。

//a1.cpp   
#include <fstream>   
#include <iostream>   
#include "a3.cpp"   
using namespace std;  
Write a;  
int main()  
{  system("pause");  return 0;  
}  
//a2.cpp   
#include <fstream>   
using namespace std;  
extern ofstream out("a6.txt");  
//a3.cpp   
#include <fstream>   
using namespace std;  
extern ofstream out;  
class Write  
{  
public:  Write()  {  out<<"a4.txt";  }  };  

a2.cpp中,定义了全局变量 out,a3.cpp中定义了一个类 Write,它的构造函数初始化依赖于 out,因为2个文件的便宜顺序是不确定的,所以,很有可能 当Write()调用的时候,out全局变量并没有被初始化,造成程序错误。

为了解决这个问题,可以将每个non-local-static对象搬到自己的专属函数内,即成为 local-static对象,然后函数返回该local-static对象的引用,然后用户调用这些函数,而不直接指涉这些对象。可以这么做是因为:c++保证,函数内的local-static对象会在函数被调用期间,首次遇上该对象定义式时被初始化,所以保证外部使用的static对象是经过初始化的。


使用时调用,单例模式,多线程不安全。



(二)、构造/析构/赋值运算

五、C++默认编写的函数

默认构造、复制构造、析构、赋值运算符

六、拒绝自动生成的函数

拒绝编译器自动生成拷贝构造函数和重载赋值运算符, 可以:

(1)私有化拷贝构造和赋值运算符;

class Test

{

public:

Test();

private:

Test(const Test&);//只声明,不进行实现。

Test & operator=(const Test&);
}

(2)通过私有继承UnCopyable手工类。

class Test: private UnCopyable  //通过继承 UnCopyable类,当编译器想要为Test类自动生成代码时,会调用Uncopyable类的相应函数,触发错误。

{

}

七、多态基类声明虚析构函数

(不)具有多态性质基类(不)需要虚析构函数;

任何class只要带有virtual成员函数,就需要带有一个virtual 析构函数。

如果通过多态的方式将一个基类的指针指向一个子类的对象(该对象位于堆内存中),如果想要delete该子类对象,通过delete 指向该对象的基类指针来操作。如果基类的析构函数为 virtual,则可以完美的删除子类对象,否则会出现局部删除的现象(只删除基类的内容),造成内存泄露。

class 设计的目的如果不是作为base class使用,或者不具备多态性,就不该声明为virtual。


八、不让异常逃出析构

(1)析构函数绝对不要吐出异常,如果一个析构函数调用的函数可能发生异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或者结束程序

(2)如果客户需要对某个函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。


九、不在构造和析构中调用虚函数

调用后仅仅是自身的虚函数,而非子类(构造是先构造父类,此时子类的信息还不知道;析构先析构子类,当析构父类时,子类的信息已经清除)。

需要子类构造信息解决方案:子类使用静态函数构造基类的参数。


使用辅助函数 static std::string createLogString(parameter)创建一个值给base class 比在成员初始值列列出所有的实参,更加方便。注意辅助函数应该为静态函数,使得它不可能意外指向“初期未成熟的子类对象中的尚未初始化的成员变量”。(静态成员函数内部不能使用非静态成员变量)


十、operator=返回*this的引用

允许连续赋值。

int x = y = z = 15; 其处理过称为 x = (y = (z = 15)) , 其中,= 赋值操作符返回的是 =(赋值操作符)左侧参数的引用,即 z = 15 返回 z 的引用。整个式子相当于, z = 15, y = z, x = y.

为了实现连锁赋值,必须使得赋值操作符返回一个reference指向操作符的左侧实参。这是为class实现赋值操作的协议:


这个协议不仅适用于 = 赋值操作符,也适用于所有与赋值相关的操作符。如 +=, -=等。


十一、operator=处理自我赋值

注意资源的释放顺序。


十二、复制对象要面面俱到

不要丢失基类的成员的复制。

Copying函数应该确保复制“对象中所有成员变量”以及“所有 base class成分”

拷贝构造函数是构造一个新的对象,而赋值运算符是对一个已经存在的对象进行操作。所以在拷贝构造函数中避免使用赋值运算符,在赋值运算符的重载函数中避免使用拷贝构造函数。

不要尝试以某个copying函数实现另一个copying函数,应该将共同机能放进第三个函数中,并由两个copying函数调用。


(三)、资源管理

十三、对象管理资源

构造函数获得资源,析构函数释放资源;

以对象管理资源的两个想法:

(1)获得资源后立刻放进管理对象

(2)管理对象利用析构函数确保资源被释放


使用智能指针封装:tr1::shared_ptrauto_ptr。 auto_ptr<类型> p1, tr1::shared_ptr<类型> p2,  其中p1, p2 为指针类型,不需要加 *。

使用 auto_ptr需要注意不能让多个auto_ptr指向同一个对象(防止重复删除,内存破坏)。为了预防这个问题,auto_ptrs保证若通过拷贝构造函数或赋值运算符来复制他们,他们会变成null,而所复制的指针将获得资源的唯一拥有权。



auto_ptr的替代方案是使用“引用计数型智慧指针” RCSP, RCSP持续跟踪有多少个对象指向某笔资源,并在无人指向该资源时,自动删除该资源。



1、为防止资源泄露,使用RAII对象,他们在构造函数中获得资源并在析构函数中释放资源

2、两个常用的RAII classes类是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若采用 auto_ptr, 复制行为会使得它指向null。

十四、资源管理中小心copying


class Lock
{
public:explicit Lock(Mutex* pm):mutexPtr(pm){lock(mutexPtr);};~Lock(){unlock(mutexPtr);};
private:Mutex* mutexPtr;
};//客户对lock的用法符合RAII原则
Mutex m; //定义所需要的互斥器
....
{		//建立区块用于定义关键区Lock m1(&m);	//锁定互斥器....			//执行关键区内的操作
}		//在区块最末尾,自动解除互斥器锁定

此时,若对lock对象进行复制,Lock m2(m1) 则:

互斥锁加解锁的对象禁止复制;

引用计数法,tr1::shared_ptr<Mutex> mutex_ptr(pm,unlock),含有删除器的指针。



十五、资源管理类提供原始资源访问

原始资源获取;

显式转换—— tr1::shared_ptr和auto_ptr中都提供了一个get成员函数,来执行显式转换,也即它会返回智能指针内部的原始指针(的复件)。

int days = daysHeld(pInv.get());

隐式转换——类型转换函数,使用-> 或者* 操作符进行指针的操作,达到隐式转换。


显式转换比较安全。隐式转换对客户比较方便。


十六、new-delete同型成对

[]的出现与否要对应起来,即使使用了typedef重命名了数组类型。


十七、独立成句的new对象放入智能指针

new对象转换为智能指针作为参数,可能会被编译器结合其他参数调整顺序,造成内存泄漏。

可以在使用时,分条写代码,使顺序固定。

这篇关于Effective C++ 摘记(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

【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提供个模板形参的名

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)