• Effective Modern C++[实践]->优选delete关键字删除函数,而非private未定义函数


    1. 优先选用删除函数,而不是private未定义函数
    2. 任何函数都可以删除,包括非成员函数和模板具现

    c++会在需要时自动生成成员函数

    C++中,如果没有声明自己的类型,编译器会自动为类型生成默认构造函数复制构造函数复制赋值运算符析构函数。 这些函数称为 特殊成员函数 ,它们使C++中的简单用户定义类型的行为类似于C中的结构。也就是说,您可以创建、复制和销毁它们,而无需任何额外的编码工作。 C++11 为语言带来了移动语义,并将移动构造函数移动赋值运算符添加到编译器可以自动生成的特殊成员函数列表中。
    示例1:

    class AClass{
    };
    int main(){
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时生成的类如下:

    |-CXXRecordDecl 0x130a140 <main.cpp:1:1, line:2:1> line:1:7 class AClass definition
    | |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
    | | |-DefaultConstructor exists trivial constexpr needs_implicit defaulted_is_constexpr
    | | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
    | | |-MoveConstructor exists simple trivial needs_implicit
    | | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param
    | | |-MoveAssignment exists simple trivial needs_implicit
    | | `-Destructor simple irrelevant trivial needs_implicit
    | `-CXXRecordDecl 0x130a258 <col:1, col:7> col:7 implicit class AClass
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    C++98中,如果你想要压制一个成员函数的使用,那多半是复制构造函数、复制赋值运算符或者他们两者。
    为了阻止这些函数,采取的做法通常是将其声明为private,并且不去定义他们。
    这意味着,如果一段代码仍然可以访问他们(如成员函数或者类的友元)并使用了他们呢,在链接阶段就会由于缺少函数定义而失败。

    C++11中引入了=delete。将这些函数标识为删除函数。在编译阶段就可以诊断出误用的情况。

    弃置函数=delete

    如果使用特殊语法 = delete ;取代函数体,那么该函数被定义为弃置的(deleted)任何弃置函数的使用都是非良构的(程序无法编译)。这包含调用,包括显式(以函数调用运算符)及隐式(对弃置的重载运算符、特殊成员函数、分配函数等的调用),构成指向弃置函数的指针或成员指针,甚至是在不求值表达式中使用弃置函数。但是可以隐式ODR使用刚好被弃置的非纯虚成员函数。

    好处

    阻止特殊成员函数生成

    删除特殊成员函数提供了一种更清晰的方法,可以防止编译器生成我们不想要的特殊成员函数

    class X {
    public:
      X(const X&) = delete;  // 禁止复印,但允许移动
      X& operator=(const X&) = delete;
    //明确定义或delete特殊成员函数时,
    //除此之外的特殊成员函数必须明确定义或default声明
    //不隐含定义
      X(X&&) = default;
      X() = default;
      X& operator=(X&&) = default;
    };
    
    int main(){
      X x1;
    //X x2 = x1;  // X的复制构造符被delete声明
      X x3 = X(); // OK : 移动赋值是可以的
    
      X x4;
    //x4 = x1;    // 编译错误!X的复制赋值运算符被delete声明
    
      X x5;
      x5 = X();   // OK :可以移动赋值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    删除普通成员函数或非成员函数

    删除普通成员函数或非成员函数可以防止有问题的类型提升导致调用意想不到的函数。因为已删除的函数仍然参与重载决议,并提供比在提升类型后可以调用的函数更好的匹配。

    1. 避免某些隐式转换。
    #include <iostream>
    
    using namespace std;
    
    bool isLucky(int number){
        cout<<"number=  "<<number<<endl;
        return true;
    }
    
    int main(){
     	isLucky('a');
    	isLucky(true);
    	isLucky(3.5);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出

    number=  97
    number=  1
    number=  3
    
    • 1
    • 2
    • 3

    添加如下声明后,double,bool,char都无法直接调用了。

    bool isLucky(char) = delete;
    bool isLucky(bool) = delete;
    bool isLucky(double) = delete;
    
    • 1
    • 2
    • 3

    编译报错如下:

    <source>: In function 'int main()':
    <source>:15:12: error: use of deleted function 'bool isLucky(char)'
       15 |     isLucky('a');
          |     ~~~~~~~^~~~~
    <source>:5:6: note: declared here
        5 | bool isLucky(char) = delete;
          |      ^~~~~~~
    <source>:16:12: error: use of deleted function 'bool isLucky(bool)'
       16 |     isLucky(true);
          |     ~~~~~~~^~~~~~
    <source>:6:6: note: declared here
        6 | bool isLucky(bool) = delete;
          |      ^~~~~~~
    <source>:17:12: error: use of deleted function 'bool isLucky(double)'
       17 |     isLucky(3.5);
          |     ~~~~~~~^~~~~
    <source>:7:6: note: declared here
        7 | bool isLucky(double) = delete;
          |      ^~~~
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 避免不应该进行的模板具现
    template<typename T>
    void processPointer(T* ptr)
    
    • 1
    • 2

    对于上面这个模板,若我们不需要对void*char*做处理,我们可以禁止void*char*的具现化,这个时候只要结合模板特化和delete即可实现这一功能。

    template<>
    void processPointer<void>(void*) = delete;
    template<>
    void processPointer<char>(char*) = delete;
    
    • 1
    • 2
    • 3
    • 4

    注意事项

    1. 如果函数被重载,那么首先进行重载决议,且只有在选择了弃置函数时程序才非良构:
    • 示例1: 定义为 =delete 的函数可以有其重载版本。
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class B {
        string str;
       public:
        B() = delete;
        B(string s) : str(s) {}
        B(const B &b) = delete;  // 拷贝构造函数
        B(const B &b, int): str(b.str) {}  // 拷贝构造函数的重载版本
        string get() = delete; 
        string get(int) const{return str;}
        ~B() = default;
    };
    
    int main() {
        B a("New string");
        cout << a.get(0) << endl;  // 调用重载的get()版本
        // cout<< a.get() ; //error!
        B a1(a, 1);  // 调用重载复制构造函数版本
        cout << a1.get(2);
        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
    • 示例2:在选择了弃置函数时程序才非良构
    struct sometype
    {
        void* operator new(std::size_t) = delete;
        void* operator new[](std::size_t) = delete;
    };
    sometype* p = new sometype; // 错误:尝试调用弃置的 sometype::operator new
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 函数的弃置定义必须是翻译单元中的首条声明,已经声明过的函数不能声明为弃置的:
    struct sometype { sometype(); };
    sometype::sometype() = delete; // 错误:必须在首条声明弃置
    
    • 1
    • 2

    参考

    [1] Function declaration
    [2] Explicitly Defaulted and Deleted Functions in C++ 11
    [3] 関数のdefault/delete宣言
    [4] Item11 Prefer deleted functions to private undefined ones

  • 相关阅读:
    动态SQL
    玩客云刷机
    【编译原理】类型检查
    BLUE legend传奇引擎不使用路由器架设单传奇的办法
    第3章“程序的机器级表示”:异类的数据结构
    spring-kafka中ContainerProperties.AckMode详解
    ctfshow--RCE极限挑战
    手写 Attention & 迷你LLaMa2——LLM实战
    【Maven学习】3.5 实验五:让 Web 工程依赖 Java 工程
    《数据库系统概论》教学上机实验报告
  • 原文地址:https://blog.csdn.net/MMTS_yang/article/details/125557258