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++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的