effectiv c++条款1-11

条款2: 尽量以const, enum, inline 替换#define

#define port 8888
  • define可以定常量值,但是它没有作用域,并且它是直接被替换,如果是大型项目,报错显示的是8888而不是port,导致无法追踪该错误。这个问题也会出现在记号表调试中,define的常量没有进入记号表中。具体原因可以看ELF文件解析
  • define不能被封装,而const可以被封装在类中。
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
  • 类似于上面定义的宏函数,必须要记住为宏中的所有实参加上小括号,否则有可能出现错误。define定义的宏函数的好处就是可以避免函数调用带来的额外开销。我们可以用inline替换
因为宏函数的参数没有类型,所有我们用模板实现。
template<typename T>
inline void callwithmax(const T& a, const T& b)
{
	f((a) > (b) ? (a) : (b));
}

有了const、enum和inline,我们对预处理器(特别是#define)的需求降低,但并非完全消除。#include仍然是必需品,而#ifdef和#ifndef也继续扮演着控制编译的重要角色。

宏函数为什么一般要用do while(0)包含起来?

#define test() \
printf("\\\\\\n");\
printf("######\n");

int main()
{
    if(false)
        test();
}

看上面这段代码,if条件不成立,不会执行test宏函数,但是却打印出来了"#####"。是因为宏函数直接展开变为如下代码所示

if(false)
	printf("\\\\\\n");
	printf("######\n");

所以一般会加上do while(0)将宏函数内部代码包含在一起,封装了作用域。

条款3: 尽可能使用const

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
    代码如下

class TextBlock
{
public:
	...
	const char& operator[](std::size_t position) const
	{
		....
	}
	char& operator[](std::size_t position)
	{
		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
		//先将*this转换为const调用const的[]函数,然后将返回值去除const
	}
};

const成员调用非const成员函数绝对是一个错误的行为。但是非const成员可以调用const成员函数。

条款4:确保对象使用之前已被初始化

  • 内置类型对象进行手工初始化,因为c++不保证它们的初始化
  • 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

条款5:了解C++默默编写并调用哪些函数

类会帮助我们实现默认的构造函数,拷贝构造函数拷贝赋值函数和析构函数,如果定义了其中一个,那么它的默认函数就不会存在,除非显示声明需要默认函数,用到 = default;

条款6:若不想使用编译器自动生成的函数,就应该明确拒绝

如果不想要编译器自动生成的函数我们有两种方法

  1. 将不想要的函数声明在private中(c+11 之前的方法),因为默认的函数都是public。
  2. 显示的= delete (c++11新出)

条款7:为多态基类声明virtual析构函数

派生类在销毁的时候首先会调用基类的析构函数,如果基类的析构函数不是虚函数,那么就会产生局部销毁,这里的局部销毁指的是只会销毁基类的部分,而派生类中的资源没有被销毁,导致资源泄漏等错误。

#include<iostream>
#include<memory>

class base
{
public:  
    base() : str(new char('a')) 
    {
        std::cout << "基类构造函数运行\n";
    }
    //virtual ~base()
    ~base()
    {
        std::cout << "基类析构函数运行\n";
        delete str;
    }

private:  
    char *str;
};

class derived : public base
{
public:  
    derived() : base(), der(new char('b')) {}
    //virtual ~derived()
    ~derived()
    {
        delete der;
        std::cout << "派生类析构函数运行\n";
    }

private:  
    char *der;
};


int main()
{
    base *c = new derived();
    delete c;

}

当我们没有将析构函数声明为虚函数时,其上述代码结果运行如下
在这里插入图片描述
上述结果显示,只调用了基类的析构函数,没有调用派生类的析构函数,会导致派生类成员得不到析构,造成资源泄漏。

现在我们将析构函数声明为虚函数,运行结果如下
在这里插入图片描述
结论:将析构函数声明为virtual析构函数。

  • 带有多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual析构函数,他就应该声明一个virtual析构函数。
  • Classes的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。如果不具备多态性声明了virtual析构函数,就是造成资源的浪费,因为virtual会对应虚函数表。

条款11: 在operator= 中处理"自我赋值"

#include<iostream>
#include<memory>

class Bitmap
{
public:  
    Bitmap() : count(new int(0)) {}
    //因为类中存在堆上的对象,所以在赋值的时候要将被赋值对象所占用的堆内存释放
    /*    
    Bitmap& operator = (const Bitmap& a)
    {
        delete count;           //如果是自我赋值,删除自身的内存在去访问会导致非法的行为,所以我们要判断是否为自我赋值。
        count = new int(*a.count);
        return *this;
    }
    */
    //方法1: 防止自我赋值出现上述问题。
    Bitmap& operator = (const Bitmap &a)
    {
        if(this == &a)
        {
            return *this;
        }
        delete count;
        count = new int(*a.count);
        return *this;
    }
    //方法2: 防止自我赋值出现上述问题。
    Bitmap& operator = (const Bitmap &a)
    {
        int *temp = count;
        count = new int(*a.count);
        delete temp;
        return *this;
    }

private:  
    int *count;
};
  • 确保当对象自我赋值时operator= 有良好的行为。其中技术包括比较“来源对象” 和"目标对象"的地址、精心周到的语句顺序、以及copy-and-swap。
  • 确保任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值