当前位置: 首页 > news >正文

C++语言编程规范-初始化和类型转换

01  

C++语言编程规范-常量

02  

初始化和类型转换

声明、定义与初始化

03  

禁止用 memcpy、memset 初始化非 POD 对象

说明:POD 全称是“Plain Old Data”,是 C++ 98 标准(ISO/IEC 14882, first edition, 1998-09-01)中引入的一个概念, POD 类型主要包括 int, char, float,double,enumeration,void指针等原始类型及其集合类型,不能使用封装和面对对象特性(如用户定义的构造/赋值/析构函数、基类、虚函 数等)。

由于非 POD 类型比如非集合类型的 class 对象,可能存在虚函数,内存布局不确定,跟编译器有关,滥用内存拷贝可能会导致严重的问题。 即使对集合类型的 class,使用直接的内存拷贝和比较,破坏了信息隐蔽和数据保护的作用,也不提倡 memcpy、memset 操作。

示例:×××产品程序异常退出(core dump)。 经过现场环境的模似,程序产生 COREDUMP,其原因是:

在初始化函数内使用

     memset(this,0,sizeof(*this))

    进行了类的初始化,将类的虚函数表指针被清空,从而导致使用空指针。

    解决方案:使用 C++构造函数初始化,不要使用 memset 函数初始化类对象。

    04  

    变量使用时才声明并初始化

    说明:变量在使用前未赋初值,是常见的低级编程错误。使用前才声明变量并同时初始化,非常方便 地避免了此类低级错误。在函数开始位置声明所有变量,后面才使用变量,作用域覆盖整个函数实现,容易导致如下问题:

    ⚫ 程序难以理解和维护:变量的定义与使用分离。

    ⚫ 变量难以合理初始化:在函数开始时,经常没有足够的信息进行变量初始化,往往用某个默认的空值(比如零)来初始化,这通常是一种浪费,如果变量在被赋于有效值以前使用,还会导致错误。遵循变量作用域最小化原则与就近声明原则,使得代码更容易阅读,方便了解变量的类型和初始值。特别是,应使用初始化的方式替代声明再赋值。

    示例:

      //不好的例子:声明与初始化分离string name; //声明时未初始化:调用缺省构造函数//……. name=”zhangsan”; //再次调用赋值操作符函数;声明与定义在不同的地方,理解相对困难//好的例子:声明与初始化一体,理解相对容易string name(”zhangsan”); //调用一次构造函数

      05  

      避免构造函数做复杂的初始化,可以使用“init”函数

      说明:正如函数的变量都在函数内部初始化一样,类数据成员最好的初始化场所就是构造函数,数据成员都应该尽量在构造函数中初始化。

      以下情况可以使用 init()函数来初始化:

      ⚫ 需要提供初始化返回信息。

      ⚫ 数据成员初始化可能抛异常。

      ⚫ 数据成员初始化失败会造成该类对象初始化失败,引起不确定状态。

      ⚫ 数据成员初始化依赖 this 指针:构造函数没结束,对象就没有构造出来,构造函数内不能使用 this 成员;

      ⚫ 数据成员初始化需要调用虚函数。在构造函数和析构函数中调用虚函数,会导致未定义的行为。

      示例:数据成员初始化可能抛异常:

        class CPPRule {public:   CPPRule():size_(0), res (null) {}; //仅进行值初始化  long init(int size)   {     //根据传入的参数初始化size_, 分配资源res   } private:   int size_;   ResourcePtr* res; }; //使用方法:CPPRule a; a.init(100);

        06  

        初始化列表要严格按照成员声明顺序来初始化它们

        说明:编译器会按照数据成员在类定义中声明的顺序进行初始化,而不是按照初始化列表中的顺序,

        如果打乱初始化列表的顺序实际上不起作用,但会造成阅读和理解上的混淆;特别是成员变量之间存在依赖关系时可能导致 BUG。

        示例:

          //不好的例子:初始化顺序与声明顺序不一致class Employee public: Employee(const char* firstName, const char* lastName) : firstName_(firstName), lastName_(lastName) , email_(firstName_ + "." + lastName_ + "@huawei.com") {}; private: string email_, firstName_, lastName_; };

          类定义 email_是在 firstName_,lastName_之前声明,它将首先初始化,但使用了未初始化的 firstName_和lastName_,导致错误。在成员声明时,应按照成员相互依赖关系按顺序声明。

          07  

          类型转换

          避免使用类型分支来定制行为:类型分支来定制行为容易出错,是企图用 C++编写 C 代码的明显标志。

          这是一种很不灵活的技术,要添加新类型时,如果忘记修改所有分支,编译器也不会告知。使用模板和虚函数,让类型自己而不是调用它们的代码来决定行为。

          08  

          使用 C++风格的类型转换,不要使用 C 风格的类型转换

          说明:C++的类型转换由于采用关键字,更醒目,更容易查找,编程中强迫程序员多停留思考片刻,谨慎使用强制转换。

          C++使用 const_cast, dynamic_cast, static_cast, reinterpret_cast 等新的类型转换,它们允许用户选择适当级别的转换符,而不是像 C 那样全用一个转换符。

          dynamic_cast:主要用于下行转换,dynamic_cast 具有类型检查的功能。dynamic_cast 有一定的开销,建议在调测代码中使用。

            #include <iostream>#include<typeinfo>class Base {public:    virtual ~Base() {} // 需要虚函数才能启用RTTI};class Derived : public Base {public:    void derived_func() {        std::cout << "Derived function called" << std::endl;    }};int main() {    Base* base_ptr = new Derived();    // 使用 dynamic_cast 进行安全的下行转换    Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr);    if (derived_ptr) {        std::cout << "转换成功" << std::endl;        derived_ptr->derived_func(); // 安全调用派生类函数    } else {        std::cout << "转换失败" << std::endl;    }    // 尝试转换不匹配的类型    Derived* another_ptr = dynamic_cast<Derived*>(new Base());    if (another_ptr) {        std::cout << "转换成功" << std::endl;    } else {        std::cout << "转换失败" << std::endl;    }    delete base_ptr;    delete another_ptr;    return 0;}

            对于引用类型的 dynamic_cast:

              #include<iostream>#include <typeinfo>class Base {public:    virtual ~Base() {}};class Derived : public Base {public:    void derived_func() {        std::cout << "Derived function called" << std::endl;    }};void process(Base& base) {    try {        Derived& derived = dynamic_cast<Derived&>(base);        std::cout << "转换成功" << std::endl;        derived.derived_func();    } catch (const std::bad_cast& e) {        std::cout << "转换失败: " << e.what() << std::endl;    }}int main() {    Derived derived;    Base base;    std::cout << "处理 Derived 对象:" << std::endl;    process(derived); // 转换成功    std::cout << "\n处理 Base 对象:" << std::endl;    process(base);    // 转换失败    return 0;}

              static_cast:和 C 风格转换相似可做值的强制转换,或上行转换(把派生类的指针或引用转换成基类的指针或引用)。该转换经常用于消除多重继承带来的类型歧义,是相对安全的。下行转换(把基类的

              指针或引用转换成派生类的指针或引用)时,由于没有动态类型检查,所以不安全的,不提倡下行转换。

              上行转换(派生类到基类)- 这是 static_cast 最安全的用法之一

                class Base {public:    virtual ~Base() {}    virtual void base_func() { std::cout << "Base function" << std::endl; }};class Derived : public Base {public:    void derived_func() { std::cout << "Derived function" << std::endl; }};int main() {    Derived derived;    Base* base_ptr = static_cast<Base*>(&derived); // 上行转换,安全    base_ptr->base_func(); // 调用基类函数    return 0;}

                reinterpret_cast:用于转换不相关的类型。reinterpret_cast 强制编译器将某个类型对象的内存新解释成另一种类型,相关代码可移植不好。建议对 reinterpret_cast<>的用法进行注释,有助于减少维

                护者在看到这种转换时的顾虑。

                良好的编码实践:添加注释

                  // 注意:这里使用 reinterpret_cast 是因为我们需要将特定内存区域// 解释为另一种类型,这是平台相关的操作,不具有可移植性class MemoryMappedDevice {public:    MemoryMappedDevice(void* base_addr)         : control(reinterpret_cast<volatile uint32_t*>(base_addr)),          data(reinterpret_cast<volatile uint32_t*>(static_cast<uintptr_t>(base_addr) + 4)) {}private:    volatile uint32_t* control;    volatile uint32_t* data;};

                  const_cast:用于移除对象的 const 属性,使对象变得可修改。

                    int main() {    const int ci = 10;    const int* ci_ptr = &ci;    // 移除 const 属性    int* i_ptr = const_cast<int*>(ci_ptr);    *i_ptr = 20; // 现在可以修改值    std::cout << "ci: " << ci << std::endl; // 输出可能是 20,也可能还是 10    std::cout << "*i_ptr: " << *i_ptr << std::endl; // 输出 20    return 0;}

                    09  

                      extern void Fun(DerivedClass* pd); void Gun(BaseClass* pb) //不好的例子: C风格强制转换,转换会导致对象布局不一致,编译不报错,运行时可能会崩溃DerivedClass* pd = (DerivedClass *)pb; //好的例子: C++风格强制转换,明确知道pb实际指向DerivedClass DerivedClass* pd = dynamic_cast< DerivedClass *>(pb); if(pd) Fun(pd);}

                      http://www.xdnf.cn/news/1472725.html

                      相关文章:

                    • 技术面:Java并发(线程池、ForkJoinPool)
                    • Acrobat-2025.001.20643_Win中文_PDF编辑器_便携版安装教程
                    • Go初级之十:错误处理与程序健壮性
                    • 内存纠错检错方法-SSCDSD
                    • vggt代码详解
                    • 迁移学习实战:基于 ResNet18 的食物分类
                    • BYOFF (Bring Your Own Formatting Function)解析(80)
                    • GPU集群扩展:Ray Serve与Celery的技术选型与应用场景分析
                    • Pinia 两种写法全解析:Options Store vs Setup Store(含实践与场景对比)
                    • (3)Seata AT 模式的事务一致性保证机制
                    • MySQL慢查询优化策略
                    • 洛谷 P2392 kkksc03考前临时抱佛脚-普及-
                    • 【C++题解】贪心和模拟
                    • Linux设备down机,如何识别是 断电还是软件复位
                    • Java笔记20240726
                    • 【Day 22】94.二叉树的中序遍历 104.二叉树的最大深度 226.翻转二叉树 101.对称二叉树
                    • linux上nexus安装教程
                    • 从“下山”到AI引擎:全面理解梯度下降(下)
                    • 学习心得分享
                    • 【OJ】C++ vector类OJ题
                    • 使用国内镜像源解决 Electron 安装卡在 postinstall 的问题
                    • 【Python - 类库 - BeautifulSoup】(01)“BeautifulSoup“使用示例
                    • ESP-idf注册双服务器配置
                    • SemiSAM+:在基础模型时代重新思考半监督医学图像分割|文献速递-深度学习人工智能医疗图像
                    • 笔记:现代操作系统:原理与实现(2)
                    • CLIP学习
                    • 【C++】Vector完全指南:动态数组高效使用
                    • Transformer核心—自注意力机制
                    • 大批项目经理被迫上前线,酸爽
                    • 图片在vue2中引用的方式和优缺点