C++类的实用经验

文章内容均整理自    刘光《C++程序员不可不知的101条实用经验》

1.绝不让构造函数为虚函数
  关于构造函数:
(1)构造函数是一个特殊的函数,其名称和类名称一样,并且不能指定返回值类型
(2)声明一个类时可以不定义构造函数,但这并不能说明此类就没有构造函数。编译器会自动实现一个默认构造函数,也就是说每个类都至少存在一个构造函数
(3)构造函数可接受多个参数,而且支持重载机制
(4)一个类若显示地声明了构造函数,编译器就不会生成默认构造函数
(5)一个类声明了一个非public的构造函数,编译器就不会生成公有的默认构造函数
关于拷贝构造函数的调用:
(1) 当类的一个对象去初始化该类的另一个对象时,拷贝构造函数会被调用
(2) 如果函数的形参是类的对象,调用函数进行实参和形参的结合时,拷贝构造函数会被调用
(3) 如果函数的返回值是类对象,函数调用完成返回时,拷贝构造函数会被调用
构造函数无论在什么情况下都不能声明为virtual函数:
(1)从存储空间角度分析,如果一个类存在一个虚函数,那么就会创建一个虚函数表,类就是通过此表确定函数调用,也就是动态绑定,这个虚函数表存储于对象的内存空间。那么问题来了,如果构造函数是虚的,就需要通过虚函数表调用,可是这时候对象还没有实例化,即内存空间还没有,更别说虚函数表了。既然无法找到虚函数表,那么构造函数也得不到调用。
(2)从使用角度分析,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,可见使用虚函数没有实际意义。
虚函数的作用在于通过父类的指针或引用来调用它,能够变成调用用子类的哪个成员函数
(3)从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调用父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期值执行一次,不是对象的动态行为。
(4)当一个构造函数被调用时,他首先要做的事情是初始化它的虚函数表指针。因此他只能知道他是当前类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,他是为这个类的构造函数产生代码,既不是为基类,也不是它的派生类。
虚函数表指针的状态是由最后调用的构造函数决定的。这就是为什么构造函数调用是从基类到最后一个派生类的一个顺序的原因。

2.避免在构造/析构函数中调用虚函数
小心陷阱:
(1)构造函数不能调用虚函数,因为这时虚函数表没有完成创建。基于此的所有操作最终都是非法的
(2) 构造函数中调用虚函数,此调用不会沿着继承调用向下传递,无论在构造函数中调用的是否是虚函数,最终调用的也只是奔雷的函数
请谨记:
不要在类的构造函数或者析构函数中调用(直接或间接)虚函数,因为这样的调用永远不会沿类继承树往下传递到子类中去

3.不使用编译器自动生成的函数,就明确拒绝
请谨记:
(1)为禁止编译器自动提供的机制,可将相应的成员函数声明为private并不予以实现
(2)将类的拷贝构造和拷贝赋值声明为private,可禁止对象拷贝。如果将构造函数声明诶private,还可实现另外的功能,这就是控制对象生成的实例数,单例模式就是采用这种机制。
class CSingleton
{
        public:
                static CSingleton* GetInstance()
                {
                        static CSingleton instance;
                        return &instance;
                }
        private:
                CSingleton(){}        //没有实现,避免成员或友元函数调用
                virtual ~CSingleton(){}        //没有实现
};
因为构造函数是private的,因此不能为CSingleton类定义实例,而只能通过static的GetInstance函数接口生成此类的实例。由于GetInstance接口采用static属性没导致生成实例只能为一个,所以最终也就实现了类对象单一性的控制

4.struct和class的关系
区别:
(1)花括号初始化
class和struct如果定义了构造函数,都不能使用花括号进行初始化。如果没有定义构造函数,struct可以用花括号初始化,若成员变量都是public的话,class也可以用花括号初始化。
(2)默认访问权限
如果不加说明的话,struct默认是public,class默认是private
(3)默认继承方式
struct默认是public继承,class默认是private继承。 class和struct之间可以相互继承,是public继承还是private继承取决于子类而不是基类,不如class继承struct就是private继承,struct继承class就是public继承

5.class对象大小与什么有关系
①无虚函数类对象大小影响因素
(1)class对象非静态数据成员占用内存大小会影响对象大小
(2)class对象采用的内存对齐策略会影响类对象大小
(3)class对象中函数成员不会影响对象大小
②包含虚函数类对象大小影响因素
(1)class对象数据成员占用内存大小会影响类对象大小
(2)class对象采用的内存对齐策略会影响类对象大小
(3)class对象中普通函数成员不会影响对象大小,但虚幻书会占用4bit
虚继承的情况:由于涉及虚函数表和虚基表,会同时增加一个(多继承下对应多个)指向虚函数表的指针和一个指向虚基表的指针,两者占用8bit
影响class对象大小的因素:
(1)空类,单一继承的空类,多继承的空类所占空间大小为1字节
(2)一个类中,虚函数本身,成员函数(包括静态和非静态)和静态数据成员都不占用类对象的存储空间
(3)一个对象的大小等于所有非静态数据成员的大小,若有虚函数就加4bit, 虚继承情况会加n*8bit,看有几个继承关系

6.将成员变量声明为private
访问权限说明
(1)公有继承
基类的public成员在派生类中为public属性
基类的protected成员派生类中为protected属性
基类的private成员在派生类中不能访问
注:访问protected成员时采用在public函数中建立接口函数,从而进行访问
(2)保护继承
基类的public成员在派生类中为protected属性
基类的protected成员在派生类中为protected属性
基类的private成员在派生类中不能访问
4.私有继承
基类的public成员在派生类中为private属性
基类的protected成员在派生类中为private属性
基类的public成员在派生类中不可访问
请谨记:
(1)将数据成员声明为private,可带来访问数据的一致性,并可为以后提供足够的弹性
(2)将数据声明为protected并不比public更具有封装性。

7.关于对象复制的思考
关于拷贝构造:
(1)拷贝构造是一种特殊的构造函数,这种函数具有单个形参,该形参是该类类型的引用。当定义一个新对象并用一个同类型的对象对其进行初始化时,复制构造函数将被调用。
(2)如果自定义的类没有提供拷贝构造函数,编译器将会合成一个。合成的赋值构造函数将逐个成员初始化,将新对象初始化为元对象的副本
请谨记:
对象复制是,请记得复制对象的所有成员及基类的所有成员

8.首选初始化列表实现类成员初始化
请谨记:
(1)请选择初始化列表实现类成员的初始化,因为这种实现可提高程序的执行效率
(2)如果类中包含引用或const成员,初始化列表是唯一可以为这些成员初始化的方法。因为在构造函数中无法对这类成员进行初始化工作。

9.理解常量成员函数
定义:声明带有const关键字的成员函数指定,函数是“只读”函数,在它被调用的时候不会修改对象。一个const成员函数不能修改任何非静态数据成员或调用不是cosnt类型的成员函数。
const函数规则:
(1)const对象只能访问const成员函数,而非const对象可以访问任意成员函数,包括const成员函数
(2)const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的
(3)const成员函数不可以修改对象的数据,不管对象是不是具有const性质。
(4)添加mutlab修饰符的数据成员在任何情况下可以通过任何手段进行修改,自然此时的const成员是可以修改的
请谨记:
(1)const成员函数是class中比较特殊的函数,这种函数不能调用非const成员函数
(2)声明为const的对象,只能调用const成员函数,而不能调用其他成员函数





















class CSingleton
{
        public:
                static CSingleton* GetInstance()
                {
                        static CSingleton instance;
                        return &instance;
                }
        private:
                CSingleton(){}        //没有实现,避免成员或友元函数调用
                virtual ~CSingleton(){}        //没有实现
};

class CSingleton
{
        public:
                static CSingleton* GetInstance()
                {
                        static CSingleton instance;
                        return &instance;
                }
        private:
                CSingleton(){}        //没有实现,避免成员或友元函数调用
                virtual ~CSingleton(){}        //没有实现
};

class CSingleton
{
        public:
                static CSingleton* GetInstance()
                {
                        static CSingleton instance;
                        return &instance;
                }
        private:
                CSingleton(){}        //没有实现,避免成员或友元函数调用
                virtual ~CSingleton(){}        //没有实现
};

猜你喜欢

转载自blog.csdn.net/sinat_39061823/article/details/80569343