CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章C++惯用法与ModernC++篇

    • 1.1 C++ RAII惯用法
    • 1.2 pimpl 惯用法
    • 1.3 拥抱C++新变化(C++11/14/17新增的实用特性)
    • 1.4 统一的类成员初始化语法与 std::initializer_list<T>
    • 1.5 C++17注解标签(attributes)
    • 1.6 final/override/=default/=delete语法
      • 1.7 auto关键字的前尘后事
      • 1.8 Range-based 循环语法
      • 1.9 C++17结构化绑定
      • 1.10 stl容器新增的实用方法介绍
      • 1.11 stl中的智能指针类详解
    • 第2章C++开发工具与调试进阶

    • 第3章C++多线程编程从入门到进阶

    • 第4章C++网络编程重难点解析

    • 第5章网络通信故障排查常用命令

    • 第6章网络通信协议设计

    • 第7章高性能服务结构设计

    • 第8章Redis 网络通信模块源码分析

    • 第9章 服务其他模块设计

    • C++后端开发进阶
    • 第1章C++惯用法与ModernC++篇
    zhangxf
    2023-04-03
    目录

    1.6 final/override/=default/=delete语法

    final、override、=default、=delete是C++11添加的一组非常具有标记意义的新语法,我们来逐一介绍它们。

    # 1.6.1 final关键字

    final关键字修饰一个类,这个类将不允许被继承,这在其他语言(如Java)中早就实现了。在C++ 11中final关键字要写在类名的后面,其他语言是写在class关键字的前面。示例如下:

    class A final
    {
    
    };
    
    class B : A
    {
    
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    由于类A被声明成final,B继承A,编译器会报如下错误提示类A不能被继承:

    error C3246: 'B' : cannot inherit from 'A' as it has been declared as 'final'
    
    1

    # 1.6.2 override关键字

    C++语法规定,父类中加了virtual关键字的方法可以被子类重写,子类重写该方法时可以加或不加virtual关键字,例如像下面这样:

    class A
    {
    protected:
        virtual void func(int a, int b)
        {
        }
    };
    
    class B : A
    {
    protected:
        virtual void func(int a, int b)
        {
        }
    };
    
    class C : B
    {
    protected:
        void func(int a, int b)
        {
        }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    这种宽松的规定可能会带来两个问题:

    • 当我们阅读代码时,无论子类重写的方法是否添加了virtual关键字,我们都没法直观地确定该方法是否是重写的父类方法;
    • 如果我们在子类中不小心写错了需要重写的方法的函数签名(可能是参数类型、个数或返回值类型),这个方法就会变成一个独立的方法,这可能会违背我们最初想重写父类某个方法的初衷,而编译器在编译时并不会检查到这个错误。

    为了解决以上两个问题, C++ 11引进了override关键字,其实override关键字并不是什么新语法,在Java等其他语言中早就支持。被override修饰的类方法是改写父类的同名方法,加了该关键字后在编译阶段,编译器会作相应的检查,如果其父类不存在相同签名格式的类方法,编译器会给出相应的错误提示。

    情形一 父类没有子类标记了override的方法

    class A
    {
    
    };
    
    class B : A
    {
    protected:
        void func(int k, int d) override
        {
        }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    由于父类A中没有func方法,编译器会提示错误:

    error C3668: 'B::func' : method with override specifier 'override' did not override any base class methods
    
    1

    情形二 父类有子类标记了override的方法,但函数签名不一致

    class A
    {
    protected:
        virtual int func(int k, int d)
        {
            return 0;
        }
    };
    
    class B : A
    {
    protected:
        virtual void func(int k, int d) override
        {
        }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    编译器会报同样的错误。

    正确代码:

    class A
    {
    protected:
        virtual void func(int k, int d)
        {  
        }
    };
    
    class B : A
    {
    protected:
        virtual void func(int k, int d) override
        {
        }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 1.6.3 =default标记

    如果一个C++类没有显式地给出构造函数、析构函数、拷贝构造函数、operator =这几类函数的实现,在需要它们时,编译器会自动生成;或者,在给出这些函数的声明时,如果没有给出其实现,编译器在链接时就会报错。如果使用**=default**标记这类函数,编译器会给出默认实现。我们来看一个例子:

    class A
    {
    
    };
    
    int main()
    {
        A a;
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    这样的代码是可以编译通过的,因为编译器会默认生成一个A的无参构造函数,假设我们现在给A提供一个有参数形式的构造函数:

    class A
    {
    public:
        A(int i)
        {
        }
    
    };
    
    int main()
    {
        A a;
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    这个时候编译器就不会自动生成默认无参数的构造函数了,这段代码会编译出错,错误提示A没有合适的无参构造函数:

    error C2512: 'A' : no appropriate default constructor available
    
    1

    我们这个时候可以手动给A加上无参构造函数,也可以使用**=default**语法强行让编译器自己生成:

    class A
    {
    public:
        A() = default;
    
        A(int i)
        {
        }
    
    };
    
    int main()
    {
        A a;
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    =default笔者觉得最大的作用就是,在开发中简化了那些构造函数中没有实际的初始化代码的写法,尤其是声明和实现分别属于一个*.h和*.cpp文件。例如,对于类A,其头文件为a.h ,其实现文件为a.cpp,正常情况下我们需要在a.cpp文件中写其构造函数和析构函数的实现(可能没有实际构造和析构代码):

    //a.h
    class A
    {
    public:
        A();
        ~A();
    };
    
    1
    2
    3
    4
    5
    6
    7
    //a.cpp
    #include "a.h"
    
    A::A()
    {
    }
    
    A::~A()
    {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    a.cpp中构造函数和析构函数我们不得不写上,有了**=default关键字,我们可以在a.h**中直接写成:

    //a.h
    class A
    {
    public:
        A() = default;
        ~A() = default;
    };
    
    1
    2
    3
    4
    5
    6
    7
    //a.cpp
    #include "a.h"
    //这里不用在写A的构造函数和析构函数的实现了
    
    1
    2
    3

    # 1.6.4 =delete标记

    既然有强制让编译器生成构造函数、析构函数、拷贝构造函数、operator =的语法,那么也应该有禁止编译器生成这些函数的语法,没错,就是**=delete**。

    在C++98/03规范中,如果我们想让一个类不能被拷贝(即不能调用其拷贝构造函数),可以将其拷贝构造和operator =函数定义成private的:

    class A
    {
    public:
        A() = default;
        ~A() = default;
    
    private:
        A(const A& a)
        {
        }
    
        A& operator =(const A& a)
        {
        }
    };
    
    int main()
    {
        A a1;
        A a2(a1);
        A a3;
        a3 = a1;
    
        return 0;
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    以上代码在利用a1构造a2时编译器会提示错误:

    error C2248: 'A::A' : cannot access private member declared in class 'A'
    error C2248: 'A::operator =' : cannot access private member declared in class 'A'
    
    1
    2

    我们利用了这种方式间接实现了一个类不能被拷贝的功能,这也是继承自 boost::noncopyable 的类不能被拷贝的实现原理。现在有了 =delete 语法,我们直接使用该语法,直接禁止编译器生成这两个函数即可:

    class A
    {
    public:
        A() = default;
        ~A() = default;
    
    public:
        A(const A& a) = delete;
    
        A& operator =(const A& a) = delete;
    };
    
    int main()
    {
        A a1;
        //A a2(a1);
        A a3;
        //a3 = a1;
    
        return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    一般在一些工具类中,我们不需要用到构造函数、析构函数、拷贝构造函数、operator=这四个函数,为了防止编译器自己生成,同时也是为了减小生成的可执行文件的体积,笔者建议使用 =delete 语法将这四个函数“删除”,例如:

    class EncodeUtil
    {
    public:
        static std::wstring EncodeUtil::AnsiToUnicode(const std::string& strAnsi);
        static std::string UnicodeToAnsi(const std::wstring& strUnicode);
        static std::string AnsiToUtf8(const std::string& strAnsi);
        static std::string Utf8ToAnsi(const std::string& strUtf8);
        static std::string UnicodeToUtf8(const std::wstring& strUnicode);
        static std::wstring Utf8ToUnicode(const std::string& strUtf8);
    
    private:
        EncodeUtil() = delete;
        ~EncodeUtil() = delete;
    
        EncodeUtil(const EncodeUtil& rhs) = delete;
        EncodeUtil& operator=(const EncodeUtil& rhs) = delete;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    上次更新: 2025/04/01, 20:53:14
    1.5 C++17注解标签(attributes)
    1.7 auto关键字的前尘后事

    ← 1.5 C++17注解标签(attributes) 1.7 auto关键字的前尘后事→

    最近更新
    01
    C++语言面试问题集锦 目录与说明
    03-27
    02
    第四章 Lambda函数
    03-27
    03
    第二章 关键字static及其不同用法
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式