C++编程惯用方法(3)

C++中RAII的惯用方法 软件开发中,会用到各种各样的资源。狭义的资源指内存,而广义的资源包括文件、网络连接、数据库连接、信号量、事件、线程、内存等,甚至可以是状态。资源获取后由于种种原因导致永久不能释放的资源称为资源泄漏。针对资源泄漏,提出了各种各样的软件机制和程序设计惯用法,如垃圾收集、RRID[1]、RAII、确定性资源清理等。 RAII是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造 阅读详情


一. 纯虚函数

1.
class Vehicle
{
public:
 virtual accelerete(double) = 0;
};

我们不希望出现Vehicle的对象,只是将Vehicle作为基类来使用,并且所有的派生类都重写accelerete方法,
甚至不希望有人调用Vehicle::accelerete()。
将accelerete定义为纯虚函数可以实现我们的要求。

在虚函数声明的参数列表后面加上“=0”:
class C
{
 virtual void f() = 0; 
};

我们不需要在此为纯虚函数C::f()提供任何定义。那些声明(或继承)了纯虚函数的类就是抽象类。任何试图创建
一个抽象基类对象的操作都将导致编译期错误的产生。
 
如果一个类派生自C 并重写了C::f(),它就将成为具体(非抽象)类:
class D:public C
{
 void f();
};

我们通常都将抽象基类用于接口的声明,这时我们不需要为该接口声明一个完整的实现。该接口描述了所有派生自该类
的对象都应该支持的抽象操作;派生类必须实现这些抽象操作。

class Vehicle
{
public:
 virtual double accelerete(double) = 0;
 virtual double speed() = 0; 
};
由于Vehicle是一个抽象类,任何创建Vehicle的对象的尝试都会导致编译期错误的产生。

Vehicle v;  //编译期错误:Vehicle 是抽象类。

class Car: public Vehicle
{
public:
 virtual void accelerete(double);
 virtual void speed(); 
};

class Bicycle:public Vehicle
{
public:
 virtual double accelerete(double);
 virtual double speed(); 
};

由于在Car和Bicycle中,基类中的所有纯虚函数都被重写了,所以我们就可以得到这两个类创建出来的对象。
即使我们得不到Vehicle的对象,我们仍然可以使用指向Vehicle的指针和引用:

void full_stop(Vehicle &v)
{
 v.accelerete(-v.speed());
}

一个继承(而没有重写)了纯虚函数的类同样也是一个抽象类:
class Land_Vehicle: public Vehicle
{
  
};

Land_Vehicle v;  //编译期错误:Land_vehicle是个抽象类。

对于那些类似于Vehicle这种“用来描述一系列的派生类”的类,我们总是推荐使用纯虚函数和抽象类的做法。

析构函数不能说明为纯虚的:
class Vehicle
{
public:
 virtual ~Vehicle() = 0; //不好的做法。 
};

在Vehicle的任意派生类中,在调用它们的析构函数是都会调用到Vehicle::~Vehicle()。
由于我们必须为该析构函数提供一个定义(否则就会得到一个链接错误),因此将它声明为纯虚函数毫无意义。


二. 关于继承的细节和陷阱

1. 没有被继承的东西

当我们使用继承时,要时刻牢记,下面的这些东西没有从基类中被继承:
 (1). 构造函数(包括复制构造函数)。如果我们没有声明复制构造函数,那么编译器将会自动为我们创建一个。
      在这个被创建的复制构造函数中,它会调用类中所有的非静态的数据成员以及基类的复制构造函数。
 (2). 析构函数。如果我们没有声明析构函数,并且所有的非静态数据成员和基类都有析构函数,编译器就会为
   我们自动生成一个这样的析构函数:它会调用那些非静态数据成员以及基类中的析构函数。如果类中的某
   个基类的析构函数是虚函数,那么这个合成的析构函数也将是虚函数。
 (3). 赋值操作符。如果没有声明赋值操作符,编译器也会自动合成一个:它会去调用所有非静态数据成员和基
   类中的赋值操作符。
 (4). 被隐藏的成员函数。如果在基类中存在的成员函数在派生类中没有被重写,并且在派生类中还声明了一个
      和该函数有着相同名字但参数列表不同的成员函数,那么在基类中的那个成员函数就将被隐藏。
  例如:
  class Car
  {
  public:
   void steer(int degrees); 
  };
  
  class AutoPilot
  {
  public:
   AutoPilot(); 
  };
  
  class Smart_car:public Car
  {
  public:
   void steer(AutoPilot); //Car::steer(int) 被覆盖了。 
  };

  Smart_car c;
  c.steer(45); //编译期错误:无法将AutoPilot转化为int
  
  如果不希望隐藏基类中的函数,就应该在派生类中对他们进行重新声明:
  class Smart_car : public Car
  {
  public:
   void steer(int i)
   {
    Car::steer(i);
   } 
   
   void steer(AutoPilot&);
  };
  
2. 在派生类中指定virtual关键字
   当重写一个虚函数(不管它是不是纯虚函数)时,不需要明确地使用virtual 这个关键字;编译器会自动将这个和
   基类中有着同样名字及参数列表的函数视为虚函数:
 class Base
 {
 public:
     virtual void f();
 };

 class Derived: public Base
 {
 public:
  void f();  //等同于“virtual void f()”. 
 };
   不过最好还是指定virtual这个关键字,这会使阅读我们的代码的人更容易了解代码。并且使用virtual与否不会对
   程序的意思带来任何影响。
  
3. 在构造函数和析构函数中调用虚函数
 当在构造函数和析构函数中调用虚函数时,它的运行结果可能会有所不同。当构造函数正在创建派生类中的基类部
 分时,被构造的对象就被视为基类(而不是派生类)的一个对象。这意味着调用的虚函数将会是正在被构造的基类中
 的那个成员函数,而不是派生类中的成员函数。
 例如:
 class Base
 {
 public:
  Base();
  virtual void debug_print()
  {
   cout<< "Base::Base();/n" <<endl; 
  } 
 };
 
 class Derived : public Base
 {
 public:
  Derived();
  virtual void debug_print()
  {
   cout<< "Derived::Derived();/n"<<endl; 
  } 
 };
 
 Base::Base()
 {
  debug_print(); 
 }

 main()
 {
  Base b;
  Derived d; 
 }

 上面的程序会如下输出:
 Base::Base();
 Base::Base();
 即使正在构造的是Derived中的Basebuf,在Base的构造函数中调用的debug_print也会是Base::debug_print().
 出现此情况的原因是:对象的基类部分的构造要早于其数据成员。当Derived中的Base部分被构造时,Derived
 中的其他数据成员还都没有被构造。此时调用Derived中的虚函数将变得毫无意义:因为它可能会去试图访问Derived
 中那些还没有被初始化的数据成员。(换个角度来看,当Base的构造函数被调用时,正在被构造的对象实际上不是Derived,
 因此 不可能调用到 Derived的成员函数。)

 该逻辑也存在于析构函数中调用虚函数这种情况:
 Base::~Base()
 {
  debug_print();
 }

 上面的析构函数调用中,调用的总是Base::debug_print(),即使当被摧毁的是Derived中的Base部分是也是如此。
 当我们调用Base 的析构函数时,Derived中的数据成员已经早就被析构了,因此再去调用Derived中的debug_print()
 也将变得毫无意义。
 
 记住:只有在构造和析构的过程中调用虚函数才会导致这种特殊情况的出现。在其他情况下,虚函数的行为都是正常的:
 Base::Base()
 {
  debug_print();  //调用的是Base::debug_print().
  Base *bp = new Derived;
  bp->debug_print();   //调用时的是 Derived::debug_print(). 
 }

总结:
   1. 继承描述的是is-a 的关系:派生类所实现的对象应该是基类所实现的对象集中的一个子集。
   2. 当继承是接口的一部分时,请使用公有继承。只有当继承被用来隐藏实现细节时,才需要使用私有继承和
      保护继承。
   3. 对于私有继承的大部分使用,我们都可以用组合来替代;只有当派生类需要重写(私有)基类中的虚函数时,
      才不能那样做。
   4. 在派生类中被重写的虚函数应该和基类中的抽象模型相一致。
   5. 构造函数,析构函数以及赋值操作符不能被继承。


多重继承总结:
   1. 只有在派生类包括多个基类,并且这多个基类间没有继承关系时,我们才应该使用多重继承。
   2. 使用虚基类来避免在同一对象中出现多分基类子对象。
   3. 基类对象的构造顺序和它们在类声明中(而不是在构造函数的定义中)的顺序一样,析构相反。
   4. 为每个基类明确地指定存取类型。


 

 

  
  
  
  

C++编程之自定义日志类 ——log4cpp使用详解 C++编程之自定义日志类 ——log4cpp使用详解log4cpp简介与安装log4cpp安装log4cpp简单介绍layout布局——日志输出格式log4cpp::BasicLayoutlog4cpp::PatternLayoutappenderlog4cpp::FileAppenderlog4cpp::RollingFileAppenderlog4cpp::OstreamAppenderlog... 阅读详情

相关推荐

C++ 一个函数调用其他.cpp文件中的函数

使用VC或VS创建C++项目的时候,会自动产生许多文件夹,其中有一个文件夹->源文件: 在该文件下可以自定义许多.cpp文件,但是需要注意的是这里面的各个文件只能有一个文件中含有main()函数, 而且各个文件中不能使用相同的函数名进行定义; 那么要那么多文件放在项目中有什么用呢? 当然这里C++是提供一个文件调用其他文件中函数的功能的, 这就可以让我们自定义一个只包含main()...

⚡ 4万+

C++四种编程范式及例子

C++四种编程范式及例子1. 面向过程(procedure programme, PP)2. 面向对象(Object Oriented Programming, OOP)3. 泛型编程(Generic Programming,GP)4. 函数编程(Lambda 表达式实现)参考 这两天正好听了 胡船长 四堂很不错的网课,把第一堂课涉及到的C++中用四种方式实现add()即两个数加法运算总结一下,顺便补充一下自身的理论知识。 1. 面向过程(procedure programme, PP) 面向过程编程,也被

qq_37848398的博客 7192

C++编程新手到高手:CS42Cpp课程学习之路

本文还有配套的精品资源,点击获取 简介:"CS42Cpp"课程设计为初学者提供了一个系统的C++学习路径,涵盖了从基础知识到高级概念的全面内容。课程分为两个部分:前4周的基础阶段,后16周的深入阶段,逐渐引导学员掌握C++的语法、面向对象编程、高级特性如模板和STL使用。课程利用"CS42Cpp-master"资源包提供实践材料,帮助学员通过实际编码提高编程技能和解决问题的...

weixin_35982453的博客 1605

【初识C++】熟悉C++语言的语句、语法组成和基本编程方式,可以解决一般的算法问题

熟悉C++语言的语句、语法组成和基本编程方式,可以解决一般的算法问题。我打算是将函数以及多态继承分开写,毕竟这对于我来说是一份学习笔记,最重要的还是快速复习到知识点,虽然我看到很多的C++教程都是一文到底,但我觉得这样并不好,内容覆盖广泛了,但细微之处就比较的省略了,我更希望的是它能像一本英语词典那样,在对某个单词不熟悉时,方便查找。这也是个人的一个风格,字数多了我就觉得文章应该像1、2、3……那样排列下去。

m0_62919535的博客 1866

HID设备开发避坑指南:如何利用Windows内置驱动快速实现USB键盘模拟

本文详细解析了HID设备开发中的免驱方案与USB键盘模拟技术,重点介绍了HID协议的核心优势、开发基础架构及Windows平台的实现方法。通过实战案例和代码示例,帮助开发者快速掌握USB键盘模拟技术,避免常见开发陷阱,提升设备兼容性和性能。

lambda的博客 683

基于非接触式微波雷达设计的跌倒检测预警系统【短信报警】

本项目选择基于非接触式微波雷达的跌倒检测预警装置作为技术核心,采用云帆瑞达的R60AFD1跌倒检测模组。这一模组能够根据人体的运动变化,如速度、距离和姿态等特征,判断是否发生跌倒,并可以持续监控老年人是否处于危险状态。此外,本项目还结合4G通信技术,通过Air724UG模块实现短信报警功能,一旦检测到跌倒事故,系统将自动通过4G网络向紧急联系人发送短信报警,及时通知亲属或医护人员进行救援。

1755

C++基础入门学习笔记

C++基础入门 1.1 hello world #include<iostream> using namespace std; int main() { cout << "Hello world" << endl; system("pause"); return 0; } 1.2 注释 作用:在代码中加一些说明和解释,方便自己或其他程序员程序员阅读代码 两种格式 单行注释:// 描述信息 通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明

weixin_43544679的博客 8479

如何借助Gemini 3 Pro完成一篇文献精读?(劝收藏)

金句必须是原文的完整句子,并附上该句子在文中的大致位置。你是一名资深科研文献评估专家,请根据我提供的文献信息(标题、作者、期刊、时间、引用、摘要、结论、图表)依次输出:①影响力评估:按期刊、作者、时间、引用、问题重要性给出1–10分及简述;用上述方法再结合Gemini 3 Pro的辅助,原本需要3小时才能读完一篇文献,现在只需50分钟,不管是顶刊论文还是领域内冷门论文,都可以用这套方法,另外七哥设计的高阶精读提示词,需根据自己的文献内容做调整,希望这篇文章的方法能帮你高效精读文献,掌握文献的精华内容。

智能AI让学术科研更容易 1302

C++_chapter1_C++常用的编程方法

记录C++常用的一些编程方法,包括大小写转换,#ifdef __cplusplus, std::round() ,隐士类型转换和extern “C”等。

weixin_43916755的博客 455

微信终于支持“多设备同时在线”?我用 NAS + Docker 搭了一个云端微信系统,多端共享24小时在线

通过Docker 容器,将微信运行在云端并通过浏览器访问,实现多设备共享同一微信会话 📱。本文详细介绍完整部署流程、docker-compose 配置及资源控制方案,让微信变成“可访问的云桌面应用” ✨

XiaoqiangClub的博客 872

多模态论文笔记——BLIP

本文详细介绍这几年AIGC火爆的隐藏功臣,多模态模型:BLIP。

haopinglianlian的博客 2454

C++编程技法大全(非常详细)零基础入门到精通,收藏这一篇就够了

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…上例,无论如何离开函数,以及文件打开后发生了什么,我们都不需要显式关闭文件,也不需要在函数内处理异常(例如,try-finally),这就是RAII的妙用。即使抛出异常,程序仍然处于有效状态。网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

leah126的博客 4792

11-16 Fluent结果查看及后处理功能(做动画、监控点)

监控点、面及动画操作

zheaa的博客 1万+

《C++编程惯用法——高级程序员常用方法和技巧》——2.3 公用数据

本节书摘来自异步社区出版社《C++编程惯用法——高级程序员常用方法和技巧》一书中的第2章,第2.3节,作者: 【美】Robert B. Murray ,更多章节内容可以访问云栖社区“异步社区”公众号查看。 2.3 公用数据 假设有如下一个复数类: class Complex { public:    double real;    double ima...

weixin_33924220的博客 158

如何跑通SuperPoint+LightGlue的训练代码使用gluefactory进行训练和评估?

🏆本文收录于专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家!持续更新中,up!up!up!!

**My Coding Family** 1786

从win系统远程使用服务器中的nsight system、nsight compute

本文介绍了如何在Windows系统中远程使用服务器上的Nsight System和Nsight Compute工具。主要内容包括:1)双系统同步安装相同版本的Nsight工具,配置环境变量;2)命令行生成profile文件或直接运行分析;3)Windows远程连接服务器的配置方法;4)解决权限问题的具体方案,如修改文件夹权限或调整NVIDIA驱动配置。文章提供了详细的安装步骤和常见问题解决方案,适用于需要进行GPU性能分析和调试的开发人员。

Justinyh的博客 401

《C++编程惯用法——高级程序员常用方法和技巧》——1.3 请考虑边界条件

本节书摘来自异步社区出版社《C++编程惯用法——高级程序员常用方法和技巧》一书中的第1章,第1.3节,作者: 【美】Robert B. Murray ,更多章节内容可以访问云栖社区“异步社区”公众号查看。 1.3 请考虑边界条件 你可能会对我们为什么需要考虑部门主管这种情况感到惊讶。毕竟,大部分的职工还不是部门主管。现在在设计中考虑这种特殊的情况会不会...

weixin_34334744的博客 259

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C++ 常用惯用法大全

C++ 作为一门多范式编程语言,拥有丰富的编程惯用法(idioms)。这些惯用法是经验丰富的 C++ 程序员在实践中总结出的高效解决方案。

beyondmadman的博客 1016

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

c++编程惯用法读书笔记

c++编程惯用法 ----高级程序员常用方法和技巧 知识总结 第一章

1766

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

【Cpp】C和C++混合编程

一、混合编程杂绪 1.1 为什么需要混合编程 Cpp是从C演变过来的,C有很多优秀成熟项目和库,没必要在C++中重写,C++程序可以直接调用调用 庞大项目划分后一部分适合用C(底层),一部分适合用C++(中间层、上层) 1.2 混合编程的支撑 编译型程序的编译过程:源文件->目标(库)文件->可执行程序->镜像文件 任何编程语言执行时都必须是可执行程序,所以都必须先被编译成...

baymaxly的博客 7735

C++ 特有模式深度解析:Pimpl惯用法与CRTP

摘要: C++高级编程中,Pimpl惯用法和CRTP是两种重要技术。Pimpl通过指针隐藏实现细节(如使用std::unique_ptr指向实现类),有效减少编译依赖和加速编译,但需注意析构函数定义和性能开销。CRTP通过基类模板参数实现静态多态(如Base<Derived>模式),提供零开销多态和编译时接口检查,但会降低代码可读性。二者可结合使用,Pimpl适用于库开发和代码组织,CRTP适合性能敏感场景。Pimpl侧重编译隔离,CRTP关注静态多态优化,分别解决不同层面的设计问题。

qq2745567641的博客 1138

C++模板惯用法

关于 C++ 模板编程的惯用法,note-to-self + keynote + idiom case + cross-reference 式笔记 目录 模板语法模板惯用法示例 堆栈上分配编译优化的开关推导数组元素个数推导常数隐式转换的显式函数 implicit_cast推导 callable 可调用物用成员模板实现继承隐喻假设模板基类中的成员CRTP 循环模板模式结合使

把握自己。 1975

C++中的 .h 和 .cpp 详解

通俗解释:.h和.cpp差不多就像书和目录的关系吧,目录中对书中的章节和内容进行简单表示,真正的实现是在书里面的。 一般的数据,数据结构,接口,还有类的定义放在.h文件中,可以叫他们头文件,可以#include 到别的文件中。功能实现一般都放在具体的.cpp文件中,这样方便文件管理,节约时间,提高效率。 你完全可以把功能实现一并放在.h文件中,也可以#include一个.cpp文件到另一个源文件中...

a063225的博客 2万+

在C&C++中结构体的惯用方法

C和C++中结构体的惯用法差异主要体现在设计理念和使用方式上。C语言中结构体主要用于数据聚合,通过typedef简化声明、不透明指针实现封装、柔性数组处理变长数据、函数指针模拟多态等功能。而C++的结构体与类相似,默认public访问权限,常用于POD类型、值语义对象、STL容器配合及模板元编程。C++支持聚合初始化、默认成员初始化等现代特性。在混合开发时,需确保C++结构体保持POD特性以便与C交互。总体而言,C的结构体更侧重数据组织,C++的结构体则具备更丰富的面向对象特性。

weixin_39775340的博客 937

C++中.cpp和.hpp的区别

c++中 cpp和hpp 首先,我们可以将所有东西都放在一个.cpp文件内,编译器会将这个.cpp编译成.obj,即编译单元。一个程序可以由一个编译单元组成,也可以由多个编译单元组成。一个.cpp对应一个.obj,然后将所有的.obj链接起来(通过一个叫链接器的程序),组成一个.exe,即程序。如果一个.cpp要用到另一个.cpp定义的函数怎么办,只需在这个.cpp中写上它的函数声明。 链接器将所...

Qzx9059的博客 1万+

Cpp入门基础

定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中 即为命名空间的成员。命名空间中可以定义变量/函数/类型等。namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下 ⾯的rand不在冲突了。C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。

2401_86658278的博客 5188
上一篇: C++编程惯用方法(2)
下一篇: 坐班一族快速消除疲劳七妙招
General1982
博客等级 码龄19年 14粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值