C++ 编程规范-类设计中的技巧

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. 不要在类的构造函数与析构函数中调用虚函数

  1. 如果在父类的构造函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的构造函数时对象的子类部分还没有被构造出来(未成熟对象)。
  2. 如果在父类的析构函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的析构函数体时对象的子类部分已经销毁了。
  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. 析构函数的虚与非虚

  1. 父类的析构函数不一定是虚函数,但是当父类指针指向子类对象(父类引用绑定子类对象)这种多态形式存在时,父类需要写一个public修饰的虚析构函数,这样可以通过父类的接口来多态的销毁对象,否则就可能造成内存泄露。
  2. noncopyable的虚函数不是虚函数

5. 抽象类的模拟

抽象类:至少有一个纯虚函数。不能用来生成对象。

  1. 将模拟的抽象类的构造函数和拷贝构造函数都考虑用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类创建对象。

  1. 将模拟的抽象类的析构函数设置为纯虚函数,并在类定义之外为该纯虚函数定义函数体
class PVC
{
public:
	PVC() {}
	virtual ~PVC() = 0;  // 析构函数是纯虚函数
};
// 纯虚函数有实现体,可以防止每个子类都要实现虚析构函数,一些子类不想重复写析构函数,可以调用下面的实现体。
PVC::~PVC()
{
	//...
}

class subPVC: public PVC
{
public:
	subPVC() {}
	// 如果子类有析构,那么先调用子类析构,再调用父类析构。即使子类没有析构函数,那么也会调用父类的析构函数。
	~subPVC() {}  // 就算不加virtual修饰,也是虚函数。
};
  1. 将模拟的抽象类的析构函数用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修饰一下就可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值