如何设计一个 C 的类?
时间:2021-08-19 16:12:21
手机看文章
扫描二维码
随时随地手机看文章
[导读]↓推荐关注↓什么是类?我理解类是现实世界的描述,是对业务的抽象,类设计的好不好多半取决于你抽象的巧不巧。类的设计最重要的一点是要表示来自某个领域的概念,拿我最近在做的音视频剪辑来举例,剪辑业务中有轨道的概念,也有片段的概念,每个轨道可包含多个片段,这时候就有些问题需要考虑,在现实...
↓推荐关注↓ 什么是类?
我理解类是现实世界的描述,是对业务的抽象,类设计的好不好多半取决于你抽象的巧不巧。
类的设计最重要的一点是要表示来自某个领域的概念,拿我最近在做的音视频剪辑来举例,剪辑业务中有轨道的概念,也有片段的概念,每个轨道可包含多个片段,这时候就有些问题需要考虑,在现实世界中,轨道可以复制吗?片段可以复制吗?轨道可以移动吗?片段可以移动吗?
然后我们就可以进一步将现实世界中的轨道和片段抽象成类了,可分为两个类,一个轨道类,一个片段类,两个类是否需要提供拷贝构造函数和移动构造函数,完全取决于它们在现实世界的样子。
tips:类的名字应该明确告诉用户这个类的用途。
类需要自己写构造函数和析构函数吗?
反正我每次定义一个类的时候都会明确把构造函数和析构函数写出来,即便它是空实现,即便我不写编译器也会视情况默认生成一个,自动生成的称为默认构造函数。但我不想依赖编译器,也建议大家不要过度依赖编译器,明确写出来构造函数和析构函数也是一个好习惯,多数情况下类没有那么简单,多数情况下编译器默认生成的构造函数和析构函数不一定是我们想要的。默认的构造函数不会给我们的数据成员初始化,所以需要自己写一个构造函数,其实在构造函数里的语句也不能称之为初始化,那是个赋值操作,真正的初始化可以通过初始化列表方式或者声明成员时直接给初值,类似下面的代码。如果我们的类有指针数据成员,我们在某个地方为其分配了一块内存,编译器自动生成的析构函数默认是不会将这块内存释放掉的,为了规避这潜在的风险,还是自己写一个吧!
tips:编译器在某些情况下会生成移动构造函数或移动赋值运算符,但记住这些情况太麻烦了,建议手动控制,明确要的时候就自己写一个,明确不要的时候就delete掉。
类需要手动声明默认构造函数吗?
什么是默认构造函数?看下百度百科的定义:
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。如果定义某个类的变量时没有提供初始化时就会使用默认构造函数。
这和上一个问题类似,首先需要了解什么时候需要默认构造函数,看下面这段代码。当已经为一个类提供了带有参数的构造函数,编译器不会为该类再默认的生成构造函数,如果此时在其它地方以无参形式构造了该类的一个对象,编译器就会报错,找不到对应的构造函数,那怎么解决?一种方法是为类设置一个无参的默认构造函数(像下面代码这样),另一种方法是自己提供一个对应的构造函数。我倾向于后一种方式,前一种方式只能解决编译上的问题,但还有可能存在潜在的bug。
数据成员是设置private还是public还是protected?三种访问权限就不过多介绍了,说说我平时是怎么设置数据成员权限的吧!对于普通成员变量,我全是private,除非该类作为基类,而子类也需要访问父类的私有成员,这时候我会将父类的private改为protected。什么时候用public呢?一般情况下只会对某些静态常量我会考虑使用public修饰,前提是外部有访问此常量的需求。
类需要虚析构函数吗?
这个很明确,如果类会作为基类被派生时,该基类的析构函数就一定要声明为虚函数,如果某个类确定不会被派生,那就不要声明其析构函数为虚函数。
类需要提供拷贝构造函数吗?
这里需要考虑清楚,需要明确究竟是否提供,这需要结合这个类在现实生活中的实际意义,类是某个领域某个业务某个实物的抽象,假设有一个试卷类,因为试卷可以拷贝,那就明确提供拷贝构造函数,假设有一个Person类,因为不允许克隆人,那就明确禁用拷贝构造函数。这里也可以参考智能指针中的unique_ptr,该智能指针就明确禁用了拷贝操作。
类需要提供移动构造函数吗?
移动构造是C 11引入的新特性,这里涉及到左值右值等概念。
一个类具有移动构造函数才具备移动语义,如果追求资源管理的效率,move资源效率一般会比拷贝一个资源高一些。
这里重点讨论是否需要提供移动构造函数,答案还是,要想清楚,要结合实际情况,假设我们定义了一个美国总统的类,可以提供移动构造函数,因为美国总统几年就会换一个,再假设我们定义了一个美国最傻吊总统的类,那就应该禁用移动构造函数,因为只有懂王一个,永远不可移动。
排坑:赋值运算符需要考虑是否能正确的防止自身给自身赋值?
我理解类是现实世界的描述,是对业务的抽象,类设计的好不好多半取决于你抽象的巧不巧。
类的设计最重要的一点是要表示来自某个领域的概念,拿我最近在做的音视频剪辑来举例,剪辑业务中有轨道的概念,也有片段的概念,每个轨道可包含多个片段,这时候就有些问题需要考虑,在现实世界中,轨道可以复制吗?片段可以复制吗?轨道可以移动吗?片段可以移动吗?
然后我们就可以进一步将现实世界中的轨道和片段抽象成类了,可分为两个类,一个轨道类,一个片段类,两个类是否需要提供拷贝构造函数和移动构造函数,完全取决于它们在现实世界的样子。
tips:类的名字应该明确告诉用户这个类的用途。
类需要自己写构造函数和析构函数吗?
反正我每次定义一个类的时候都会明确把构造函数和析构函数写出来,即便它是空实现,即便我不写编译器也会视情况默认生成一个,自动生成的称为默认构造函数。但我不想依赖编译器,也建议大家不要过度依赖编译器,明确写出来构造函数和析构函数也是一个好习惯,多数情况下类没有那么简单,多数情况下编译器默认生成的构造函数和析构函数不一定是我们想要的。默认的构造函数不会给我们的数据成员初始化,所以需要自己写一个构造函数,其实在构造函数里的语句也不能称之为初始化,那是个赋值操作,真正的初始化可以通过初始化列表方式或者声明成员时直接给初值,类似下面的代码。如果我们的类有指针数据成员,我们在某个地方为其分配了一块内存,编译器自动生成的析构函数默认是不会将这块内存释放掉的,为了规避这潜在的风险,还是自己写一个吧!
tips:编译器在某些情况下会生成移动构造函数或移动赋值运算符,但记住这些情况太麻烦了,建议手动控制,明确要的时候就自己写一个,明确不要的时候就delete掉。
class A {
public:
A() : a_(2) {}// 一种初始化,标准初始化形式
~A() {}
private:
int a_;
int b_ = 3; // 另一种初始化
};
类需要手动声明默认构造函数吗?
什么是默认构造函数?看下百度百科的定义:
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。如果定义某个类的变量时没有提供初始化时就会使用默认构造函数。
这和上一个问题类似,首先需要了解什么时候需要默认构造函数,看下面这段代码。当已经为一个类提供了带有参数的构造函数,编译器不会为该类再默认的生成构造函数,如果此时在其它地方以无参形式构造了该类的一个对象,编译器就会报错,找不到对应的构造函数,那怎么解决?一种方法是为类设置一个无参的默认构造函数(像下面代码这样),另一种方法是自己提供一个对应的构造函数。我倾向于后一种方式,前一种方式只能解决编译上的问题,但还有可能存在潜在的bug。
class A {
A(int a) {}
A() = default;
};
数据成员是设置private还是public还是protected?三种访问权限就不过多介绍了,说说我平时是怎么设置数据成员权限的吧!对于普通成员变量,我全是private,除非该类作为基类,而子类也需要访问父类的私有成员,这时候我会将父类的private改为protected。什么时候用public呢?一般情况下只会对某些静态常量我会考虑使用public修饰,前提是外部有访问此常量的需求。
class A {
public:
constexpr static int kConstValue = 2;
private:
int a_;
};
类需要虚析构函数吗?
这个很明确,如果类会作为基类被派生时,该基类的析构函数就一定要声明为虚函数,如果某个类确定不会被派生,那就不要声明其析构函数为虚函数。
类需要提供拷贝构造函数吗?
这里需要考虑清楚,需要明确究竟是否提供,这需要结合这个类在现实生活中的实际意义,类是某个领域某个业务某个实物的抽象,假设有一个试卷类,因为试卷可以拷贝,那就明确提供拷贝构造函数,假设有一个Person类,因为不允许克隆人,那就明确禁用拷贝构造函数。这里也可以参考智能指针中的unique_ptr,该智能指针就明确禁用了拷贝操作。
类需要提供移动构造函数吗?
移动构造是C 11引入的新特性,这里涉及到左值右值等概念。
一个类具有移动构造函数才具备移动语义,如果追求资源管理的效率,move资源效率一般会比拷贝一个资源高一些。
这里重点讨论是否需要提供移动构造函数,答案还是,要想清楚,要结合实际情况,假设我们定义了一个美国总统的类,可以提供移动构造函数,因为美国总统几年就会换一个,再假设我们定义了一个美国最傻吊总统的类,那就应该禁用移动构造函数,因为只有懂王一个,永远不可移动。
排坑:赋值运算符需要考虑是否能正确的防止自身给自身赋值?
class A {
public:
A();
A(const A