前排提醒:本文不适合初学者观看

目录

更加详细地了解类类型的默认函数

默认析构函数

默认构造函数

默认拷贝构造

复制赋值运算符

移动构造和移动赋值函数(C++11)

总结

不要继承任何STL容器

不要使用异常规范

不要在构造和析构中调用虚函数


更加详细地了解类类型的默认函数

熟悉c艹的人知道, 如果我们声明一个类类型(class, struct,Union),即使什么都不写,编译器也会为你声明很多东西,比如我们下面的代码并不会报错:

using namespace std;
class nums
{
};int main()
{nums n1;nums n2(n1);nums n3;n3 = n1;n3.~nums();nums n4=move(n2);system("pause");
}

也就是说,析构,拷贝构造,移动构造,赋值操作(包括移动赋值)和构造函数都已经写好了,而且毫无以为肯定是public的,而且默认inline,如果我们不进行对应的操作比如创建复制,编译器不会自动构造这些函数,只有在后面被使用到了才会被构建。

我们一个一个讲:

默认析构函数

值得注意的是有几点:

  1. 若这满足 constexpr 析构函数的要求,则生成的析构函数为 constexpr 。 (C++20 起)
  2. 默认的析构分为弃置析构(=delete)和平凡析构和nontrivial,需要满足一些条件才会针对不同类型的类对象产生对应的析构函数:
    1. 弃置析构:非静态类类型数据成员或基类弃置析构函数,或析构函数不能访问(例如将析构函数设置为私有)
      如果delete掉析构无法初始化任何对象,就算自定义也没用,但是类定义是没任何问题
    2. 平凡析构:当且仅当基类析构非虚,基类为平凡析构,所有成员对象都有平凡析构
      平凡析构函数是不进行任何动作的析构函数。有平凡析构函数的对象不要求 delete 表达式,并可以通过简单地解分配其存储进行释放。
    3. nontrivial,除了弃置和平凡析构
  3. 如果基类或者成员对象有虚析构,那么默认析构也是虚析构

例子1 无法访问析构:

using namespace std;
class nums {
private:~nums()=default;
};
class nums1 :public nums {
};
int main() {nums1 n3; //errorn3.~nums1();//errorsystem("pause");
}

例子2 析构弃置

using namespace std;
class nums{
public:~nums() = delete;
};
class nums1:public nums {
};
int main(){nums1 n3; //errorn3.~nums1();//errorsystem("pause");
}

默认构造函数

值得注意的有几点:

  1. 若它满足对于 constexpr 构造函数的要求,则生成的构造函数为 constexpr。 (C++11 起)
  2. 默认构造也分为弃置和平凡构造(trivial)和nontrivial:
    1. 弃置构造:非静态类类型数据成员或基类弃置或者无法访问无参构造函数。非静态类类型数据成员或基类弃置或者无法访问析构函数,含有右值引用成员
      这种情况下没办法使用这个类中的非静态对象和函数,就算自定义无参的也没用,但是类定义是没任何问题的
      注意弃置和没有默认构造是两回事,如果人为加上还是没问题的,但是弃置的意思是无论如何都没有。
    2. 平凡构造:当且仅当没有虚类成员,没有虚基类,没有默认初始化器的非静态成员,每个基类和成员都有平凡构造函数。
      平凡默认构造函数是不进行任何动作的构造函数。
    3. nontrivial:除了弃置和平凡

例子1 构造无法访问

using namespace std;
class nums {
private:nums()=default;
};
class nums1 :public nums {
};
int main() {nums1 n3; //errorsystem("pause");
}

例子2 构造弃置

using namespace std;
class nums {
public:nums() = delete;
};
class nums1 :public nums {
};
int main() {nums1 n3; //errorsystem("pause");
}

例子3 析构弃置

using namespace std;
class nums {
public:~nums()=delete;
};
class nums1 :public nums {
};
int main() {nums1 n3; //errorsystem("pause");
}

例子4 析构无法访问

using namespace std;
class nums {
private:~nums()=default;
};
class nums1 :public nums {
};
int main() {nums1 n3; //errorsystem("pause");
}

例子5 右值引用

class upper
{int&& num;
};
int main()
{upper b1;//errorsystem("pause");
}

例子6 只定义拷贝构造,这个时候只是没有默认构造,定义后可以正常使用

#include<iostream>
using namespace std;
class nums{public:int z = 0;nums(const nums& n) {z = n.z;}
};
int main() {nums n; //errorsystem("pause");
}

例7 默认被删除使用有参构造

#include<iostream>
using namespace std;
class nums {
public:nums(const int n) { cout << n << endl; };nums() = delete;
};
class nums1 :public nums {
public:nums1(const int z):nums(z) {};
};
int main() {nums1 n3(1);n3.~nums1();system("pause");
}

默认拷贝构造

值得注意的几点:

  1. 若它满足对于 constexpr 构造函数的要求,则生成的复制构造函数为 constexpr。 (C++11 起)
  2. 类可以拥有多个复制构造函数,当存在用户定义的复制构造函数时,用户仍可用关键词 default 强迫编译器生成隐式声明的复制构造函数(C++11)。
  3. 省略复制及移动 (C++11 起)构造函数,导致零复制的按值传递语义。
    当操作数和返回类型是纯右值的时候,(C++17)

    T f() {return T();
    }f(); // 仅调用一次 T 的默认构造函数

    当初始化器表达式与变量类型为同一类型的纯右值时(C++17)

    T x = T(T(f())); // 仅调用一次 T 的默认构造函数以初始化 x
  4. 弃置复制构造:类内有无法复制非静态数据(弃置或者无法访问),或有无法复制直接或者虚基类(弃置或者无法访问),或有被弃置或者无法访问的析构, 或有右值引用类成员(c++11起),因为可以同时有多个复制构造,因此就算是弃置也可以用户自己定义
  5. 平凡复制构造:没有虚基类,没有虚成员函数,每个基类和每个类型的非静态成员的复制构造都是平凡的。非联合类的平凡复制构造函数,效果为复制实参的每个标量子对象(递归地包含子对象的子对象,以此类推),且不进行其他动作。不过不需要复制填充字节,甚至只要其值相同,每个复制的子对象的对象表示也不必相同。
  6. nontrivial复制构造:除了上面两个

例1 基类拷贝构造被弃置

using namespace std;
class base
{
public:base() {};base(const base&) = delete;
};
class upper:public base
{
public:};
int main()
{upper b1;upper b2(b1);//errorsystem("pause");
}

例2 右值引用的成员

class upper
{
public:upper(int&& temp):num(forward<int>(temp)) {};int&& num;
};
int main()
{upper b1(1);upper b2(b1);//errorsystem("pause");
}

例子3 有无法复制的成员

using namespace std;
class base
{
public:base() {};base(const base&) = delete;
};
class upper
{base b1;
public:};
int main()
{upper b1;upper b2(b1);//errorsystem("pause");
}

例4 默认被删除调用其他拷贝构造

using namespace std;
class nums {
public:nums() {};nums(const nums& n,const int z) {cout << z << endl; };nums(const nums& n) = delete;
};
int main() {nums n1;nums n2(n1,1);system("pause");
}

复制赋值运算符

值得注意的几点:

  1. 类可以拥有多个复制赋值运算符,如 T& T::operator=(const T&) 和 T& T::operator=(T)。当存在用户定义的复制赋值运算符时,用户仍可用关键词 default 强迫编译器生成隐式声明的复制赋值运算符。 (C++11 )
  2. 若用户自定义移动赋值,则复制复制被弃置
  3. 有const限定的或引用类型的非静态数据成员,被弃置
  4. 有无法复制的非静态成员,直接或者(虚)基类,被弃置
  5. 用户自定义析构,被弃置
  6. 因为可以同时有多个复制赋值,因此就算是弃置也可以用户自己定义
  7. 如果没有虚成员或者虚基类,每个成员和基类的赋值运算符都是平凡的,没有volitile限定的非静态数据成员(C++14),那么它就是平凡的
  8. 否则为nontrivial

例1 移动赋值被定义

using namespace std;class nums1{
public:nums1 operator =(const nums1&& n) {cout << 1 << endl;return {};};
};
int main() {nums1 n1;nums1 n2;n2 = n1;//errorsystem("pause");
}

例2 有const成员变量

using namespace std;class nums1{
public:const int num = 1;
};
int main() {nums1 n1;nums1 n2;n2 = n1;//errorsystem("pause");
}

例3 无法复制赋值的基类

using namespace std;
class nums {
private:nums operator =(const nums& n) { return n};
};
class nums1 :public nums {
public:
};
int main() {nums1 n1;nums1 n2;n2 = n1; //errorsystem("pause");
}

例4 因为析构无法赋值

using namespace std;
class nums{const int n = 0;
public:~nums() {};
};
int main() {nums n;nums n2;n2 = n;//errorsystem("pause");
}

移动构造和移动赋值函数(C++11)

编译器将默认声明一个移动构造函数,作为其类的非 explicit 的 inline public 成员,签名为 T::T(T&&)。 或者一个移动赋值运算符:作为其类的 inline public 成员,并拥有签名 T& T::operator=(T&&)。

弃置和平凡的定义与对应的复制版本一致,不再赘述,加上一点:

  1. 有用户声明的移动赋值(构造)运算符,则移动构造(赋值)被弃置。

例子1 声明移动构造后调用移动赋值

using namespace std;
class nums{public:nums(const nums&& n) {}nums() {};
};
int main() {nums n;nums n2;n2= move(n);//errorsystem("pause");
}

总结

委员会真的有大病,难怪没人跟他们玩

不要继承任何STL容器

相信各位对虚析构都很熟悉,它的作用也都知道,比如我们在使用工厂模式进行软件开发时候对内存泄漏有很好的防范作用。与纯虚析构的区别在与能不能实例化(instance)

但是遗憾的是c++中STL容器几乎都不是用虚析构的容器,比如我们常用的string,我们看一下源码:

不要使用异常规范

异常规范是在程序后写一个throw表明希望抛出什么类型的错误,比如这样:

void func()throw(char*, exception) {throw 100;cout << "[1]This statement will not be executed." << endl;
}

这表明只能抛出string类型的exception,但是实际上如果我们运行下面代码:

using namespace std;
void f()throw(string, exception) {throw 100;
}
int main() {try {f();}catch (int) {cout << "22222222222" << endl;}return 0;
}

结果表明我们抛出了int类型的错误

这是在98版本中新增的功能,在C++11后被抛弃,但是并不会有语法错误。

此外我们注意到string的析构带有关键字noexcept,我们在定义析构的时候也建议使用,其实主要目的在于确保析构不会抛出异常,因为比如我们定义了一连串的存放某种类的数组,在delete的时候,如果每个析构都出问题,那么一连串的异常会导致不明确行为。

因此我们最好在析构中捕捉异常,不让其传播。

不要在构造和析构中调用虚函数

这一点很好理解,比如子类调用构造函数,父类构造函数调用一个虚函数,那么我们生成子类的时候就调用了父类定义的虚函数而不是子类的。解决方法就是用参数传递的方式讲子类需要自定义的东西传入给父类的非虚函数中,或者用placeholder。

c++ advanced (2) details about default member functions相关推荐

  1. C++的特殊成员函数(special member functions)及其生成规则

    特殊成员函数(Special member functions)是指那些编译器可以为类自动产生的函数. C++ 中有五种特殊成员函数(special member functions): 1. 默认构 ...

  2. 【C++面向对象】类的静态成员函数(static member functions)

    一.静态成员函数的引入 在引入静态成员函数之前,C++语言要求所有的成员函数都必须经由该类的对象来调用.而实际上,只有当成员函数中有存取非静态数据成员时才需要类对象.类对象提供this指针给这种函数使 ...

  3. C++——虚函数(Virtual Member Functions) 【functions语意学】

    单继承下的虚函数 虚函数的实现: 为每个有虚函数的类配一张虚函数表(virtual table),它存储该类类型信息和所有虚函数执行期的地址. 为每个有虚函数的类插入一个指针(vptr),这个指针指向 ...

  4. 用Excel格式舍入数字

    Did you know that Excel limits the number of numbers that appear in a cell, in General format? I dis ...

  5. 如何快速修改文件重命名命名_更改Excel命名范围的地址

    如何快速修改文件重命名命名 In Excel, you can give a name to a range of cells, then use that name in a formula, or ...

  6. 突出显示中奖彩票号码

    No, I've never won the lottery, but that's probably because I don't buy tickets! Your odds of winnin ...

  7. C++11中default的使用

    在C+11中,对于defaulted函数,编译器会为其自动生成默认的函数定义体,从而获得更高的代码执行效率,也可免除程序员手动定义该函数的工作量. C++的类有四类特殊成员函数,它们分别是:默认构造函 ...

  8. Oracle 聚合函数(Aggregate Functions)说明

    Oracle Aggregate Functions用过很多,官网的说明如下: Aggregate Functions http://docs.oracle.com/cd/E11882_01/serv ...

  9. 4.1 Member 的各种调用方式

    C++支持三种类型的member functions:static.nonstatic和virtual,每一种被调用的方式都是不同的.确定一个类成员函数不是为static:1.它能直接存取nonsta ...

最新文章

  1. Matlab和Modelsim联合仿真的配置
  2. android开发计算器微积分,不到1M的良心之作!连微积分都能算的计算器APP_TOM科技...
  3. php去掉两个数组重复数据,php两个数组怎么去除重复
  4. 数据挖掘——我们能从股市数据得出什么,以及一些算法
  5. R语言学习笔记 (入门知识)
  6. Spring Boot + Mybatis多数据源和动态数据源配置
  7. 使用命令行运行 jMeter 测试项目
  8. 合并远程仓库到本地_git远程仓库创建和合并
  9. mysql创建表语句和修改表语句
  10. python创建双链表_Python双链表原理与实现方法详解
  11. 软银愿景叕10亿美元砸向无人车,Nuro投后估值27亿美元
  12. dva开发一个cnode网站(2)
  13. Bilibili拜年祭启发的小小探索
  14. 数字信号处理概览与框图
  15. Ubuntu 开机自动运行命令
  16. 关于文件句柄数和文件描述符的区分
  17. java8新特性(拉姆达表达式lambda)
  18. Ext GridPanel多选问题
  19. WEBRTC + vue 建立连接 本地测试
  20. WPF自定义仪表盘控件

热门文章

  1. 20172305 2018-2019-1 《Java软件结构与数据结构》第六周学习总结
  2. 华为交换机配置的导出和导入
  3. 关于圆柱体表面积的计算
  4. 入耳式蓝牙耳机哪款音质好?入耳式降噪蓝牙耳音质排行榜
  5. 《nature》2020.11.30期,重症COVID-19的主要遗传危险因素来自尼安德特人
  6. IT行业毕业后该去哪个城市?(附:未来十年最火工作发展趋势)
  7. 【唐老狮】字符编码(ASCII,Unicode和UTF-8)
  8. OPENGL中的glortho和glviewport
  9. Web开发四书五经之二:CSS与XML
  10. java ajax 返回 乱码,java ajax 返回 乱码