1. 优先考虑为成员变量提供访问接口
当需要对成员变量进行访问时,可以使用Private来修饰成员变量,然后提供一个public修饰的成员函数作为外接访问该变量的接口。
class A1
{
public:
int m_a;
};
class A2
{
public:
int& getA()
{
return m_a;
}
private:
int m_a;
};
A1 a1obj;
a1obj.m_a = 3;
A2 a2obj;
a2obj.getA() = 5; // 左值引用
cout << a2obj.getA() <<endl; // 输出5
2. 如何避免将父类的虚函数暴露给子类
myfunc函数是myvirfunc函数的一行通道性质的代码。非虚拟接口
class A
{
public:
void myfunc()
{
myvirfunc();
}
virtual ~A() {}
private:
virtual void myvirfunc(){
cout << "A::myvirfunc()执行了 " << endl;
}
}
class B : public A
{
private:
virtual void myvirfunc()
{
cout << "B::myvirfunc()执行了" << endl;
}
}
int main(){
A *pobj = new B(); // 父类指针指向子类对象
pobj -> myfunc(); // 调用子类的虚函数
delete pobj;
// 如果父类的虚函数没有被private修饰,可以通过这种方式调用父类虚函数。
pobj -> A::myvirfunc();
}
3. 不要在类的构造函数与析构函数中调用虚函数
- 如果在父类的构造函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的构造函数时对象的子类部分还没有被构造出来(未成熟对象)。
- 如果在父类的析构函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的析构函数体时对象的子类部分已经销毁了。
- 总结:不要在类的构造和析构函数中调用虚函数,在构造和析构函数中,虚函数可能会失去虚函数的作用而被当做普通函数看待。
class A
{
public:
A() {
f1();
}
virtual ~A() {
f2();
}
// 定义两个虚函数
virtual void f1()
{
cout << "f1执行了" << endl;
}
virtual void f2()
{
cout << "f2执行了" << endl;
}
void AClassFunc()
{
f1();
}
};
class B
{
public:
B() {
f1();
}
virtual ~B()
{
f2();
}
virtual void f1()
{
cout << "f1执行了" << endl;
}
virtual void f2()
{
cout << "f2执行了" << endl;
}
};
A* p = new B(); // 执行A的构造中的A(),然后执行B的构造中的B()。不会因为A()中的f1()是虚函数就调用B类中的f1(),以为此时B还没有构造出来。
p->f1(); // 执行B的f1();
p->f2(); // 执行B的f2();
p->AClassFunc(); // 执行B的f1()
delete p; // 执行B的~B(),然后调用父类~A()。A的析构函数有虚函数f2(),但是不会调用B的f2(),因为By已经被释放。
4. 析构函数的虚与非虚
- 父类的析构函数不一定是虚函数,但是当父类指针指向子类对象(父类引用绑定子类对象)这种多态形式存在时,父类需要写一个public修饰的虚析构函数,这样可以通过父类的接口来多态的销毁对象,否则就可能造成内存泄露。
- noncopyable的虚函数不是虚函数
5. 抽象类的模拟
抽象类:至少有一个纯虚函数。不能用来生成对象。
- 将模拟的抽象类的构造函数和拷贝构造函数都考虑用protected来修饰。
class PVC
{
protected:
PVC() {}
PVC(const PVC&) {}
};
class subPVC: public PVC
{
public:
subPVC() {}
subPVC() {}
};
// 子类如果重写了构造函数,(应该是无论子类是否重写了构造和拷贝构造)。则可以创建子类对象
subPVC subobj1;
subPVC subobj2(subobj1);
// 因为是protected修饰,所以不能构造和拷贝构造,也就是不能用PVC创建对象。
// 创建PVC对象的时候,相当于类外调用构造函数,protected修饰不允许类外调用。
PVC subobj1;
PVC subobj2(subobj1);
这样就只允许PVC的子类创建对象,而不允许PVC类创建对象。
- 将模拟的抽象类的析构函数设置为纯虚函数,并在类定义之外为该纯虚函数定义函数体
class PVC
{
public:
PVC() {}
virtual ~PVC() = 0; // 析构函数是纯虚函数
};
// 纯虚函数有实现体,可以防止每个子类都要实现虚析构函数,一些子类不想重复写析构函数,可以调用下面的实现体。
PVC::~PVC()
{
//...
}
class subPVC: public PVC
{
public:
subPVC() {}
// 如果子类有析构,那么先调用子类析构,再调用父类析构。即使子类没有析构函数,那么也会调用父类的析构函数。
~subPVC() {} // 就算不加virtual修饰,也是虚函数。
};
- 将模拟的抽象类的析构函数用protected来修饰。
class PVC
{
protected:
~PVC() {};
};
class subPVC : public PVC
{
};
// 无论子类是否写了析构函数,那么可以使用子类创建对象,以及使用子类指针。
subPVC subobj1;
subPVC subobj2(subobj1);
subPVC* pobj = new subPVC();
delete pobj;
// 不可以创建父类对象,以及父类指针。因为删除时候无法调用析构
PVC pvcobj1;
PVC pvcobj2(pvcobj1);
PVC* p1 = new PVC();
PVC* p2 = new subPVC();
6. 尽量避免隐式类型转换
class A {
public:
A(int i) { // 类型转换构造函数
cout << i<< endl;
}
// 若把构造函数加explicit
explicit A(int i) {
cout << i << endl;
}
}
A aobj = 15; // 编译器通过构造函数把数字15转换成一个A类型的类对象(临时对象),并构造在aobj预留的空间里
A aobj = A(15); // 如果加了explicit,那么上一个写法的构造函数就会报错。
7. 强制能/不能在堆上分配内存。
强制对象不允许在堆上分配内存。重载类中的operator new和operator delete操作符,使用private修饰一下就可以。
// 使用private修饰
private:
static void* operator new(size_t, size);
static void operator delete(void* phead);
// 那么下面将报错
A* pobj = new A;
delete A;
强制对象只能在对上分配内存。将类的析构函数用private修饰一下就可以。