zoukankan      html  css  js  c++  java
  • C++解析(30):关于指针判别、构造异常和模板二义性的疑问

    0.目录

    1.指针的判别

    2.构造中的异常

    3.令人迷惑的写法

    4.小结

    1.指针的判别

    面试问题:
    编写程序判断一个变量是不是指针。

    指针的判别:
    拾遗:

    • C++中仍然支持C语言中的可变参数函数
    • C++编译器的匹配调用优先级
      1. 重载函数
      2. 函数模板
      3. 变参函数

    示例1——匹配调用优先级:

    #include <iostream>
    
    using namespace std;
    
    void test(int i) // 1.重载函数
    {
        cout << "void test(int i)" << endl;
    }
    
    template
    <typename T>
    void test(T i) // 2.函数模板
    {
        cout << "void test(T i)" << endl;
    }
    
    void test(...) // 3.变参函数
    {
        cout << "void test(...)" << endl;
    }
    
    int main(int argc, char *argv[])
    {
        int i = 0;
        
        test(i);
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    void test(int i)
    

    示例2——匹配调用优先级:

    #include <iostream>
    
    using namespace std;
    
    template
    <typename T>
    void test(T i) // 2.函数模板
    {
        cout << "void test(T i)" << endl;
    }
    
    void test(...) // 3.变参函数
    {
        cout << "void test(...)" << endl;
    }
    
    int main(int argc, char *argv[])
    {
        int i = 0;
        
        test(i);
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    void test(T i)
    

    思路:

    • 将变量分为两类:指针 vs 非指针
    • 编写函数:
      1. 指针变量调用时返回true
      2. 非指针变量调用时返回false

    函数模板变参函数的化学反应:

    示例——指针判断:

    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test() { }
        virtual ~Test() { }
    };
    
    template
    <typename T>
    bool IsPtr(T* v) // match pointer
    {
        return true;
    }
    
    bool IsPtr(...)  // match non-pointer
    {
        return false;
    }
    
    int main(int argc, char *argv[])
    {
        int i = 0;
        int* p = &i;
        
        cout << "p is a pointer: " << IsPtr(p) << endl;   // true
        cout << "i is a pointer: " << IsPtr(i) << endl;   // false
        
        Test t;
        Test* pt = &t;
        
        cout << "pt is a pointer: " << IsPtr(pt) << endl; // true
        cout << "t is a pointer: " << IsPtr(t) << endl;   // false
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    test.cpp: In function ‘int main(int, char**)’:
    test.cpp:36: warning: cannot pass objects of non-POD type ‘class Test’ through ‘...’
    [root@bogon Desktop]# ./a.out 
    p is a pointer: 1
    i is a pointer: 0
    pt is a pointer: 1
    非法指令
    

    (变参函数是C语言中的东西,根本不知道对象是什么,于是会报错。)

    存在的缺陷:

    • 变参函数无法解析对象参数,可能造成程序崩溃!!

    进一步的挑战:

    • 如何让编译器精确匹配函数,但不进行实际的调用?

    示例——指针判断优化:

    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test() { }
        virtual ~Test() { }
    };
    
    template
    <typename T>
    char IsPtr(T* v) // match pointer
    {
        return 'd';
    }
    
    int IsPtr(...)   // match non-pointer
    {
        return 0;
    }
    
    #define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char))
    
    int main(int argc, char *argv[])
    {
        int i = 0;
        int* p = &i;
        
        cout << "p is a pointer: " << ISPTR(p) << endl;   // true
        cout << "i is a pointer: " << ISPTR(i) << endl;   // false
        
        Test t;
        Test* pt = &t;
        
        cout << "pt is a pointer: " << ISPTR(pt) << endl; // true
        cout << "t is a pointer: " << ISPTR(t) << endl;   // false
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    p is a pointer: 1
    i is a pointer: 0
    pt is a pointer: 1
    t is a pointer: 0
    

    (只匹配,不运行,就不会报错。)

    2.构造中的异常

    2.1 如果构造函数中抛出异常会发生什么?

    面试问题:
    如果构造函数中抛出异常会发生什么情况?

    构造函数中抛出异常:

    • 构造过程立即停止
    • 当前对象无法生成
    • 析构函数不会被调用
    • 对象所占用的空间立即收回

    工程项目中的建议:

    • 不要在构造函数中抛出异常
    • 当构造函数可能产生异常时,使用二阶构造模式

    示例——构造中的异常:

    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test()
        {
            cout << "Test()" << endl;
            
            throw 0;
        }
        virtual ~Test()
        {
            cout << "~Test()" << endl;
        }
    };
    
    int main(int argc, char *argv[])
    {
        Test* p = reinterpret_cast<Test*>(1);
        
        try
        {
            p = new Test();
        }
        catch(...)
        {
            cout << "Exception..." << endl;
        }
        
        cout << "p = " << p << endl;
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    Test()
    Exception...
    p = 0x1
    

    p指针并没有被赋值。(对象构造抛出异常,连空指针都不会返回,也就是说不会发生内存泄漏。)

    2.2 如果析构函数中抛出异常会发生什么?

    析构中的异常:

    • 避免在析构函数中抛出异常!!
    • 析构函数的异常将导致:
      1. 对象所使用的资源无法完全释放。

    3.令人迷惑的写法

    3.1 模板中的二义性

    下面的程序想要表达什么意思?

    历史上的原因:

    • 早期的C++直接复用class关键字来定义模板
    • 但是泛型编程针对的不只是类类型
    • class关键字的复用使得代码出现二义性

    typename诞生的直接诱因:

    • 自定义类类型内部的嵌套类型
    • 不同类中的同一个标识符可能导致二义性
    • 编译器无法辨识标识符究竟是什么

    示例1——能编译过的普通情况:

    #include <iostream>
    
    using namespace std;
    
    int a = 0;
    
    class Test_1
    {
    public:
        static const int TS = 1;
    };
    
    class Test_2
    {
    public:
        struct TS
        {
            int value;
        };
    };
    
    template
    < class T >
    void test_class()
    {
        T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
                   // 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
    }
    
    int main(int argc, char *argv[])
    {
        test_class<Test_1>();
        // test_class<Test_2>();
        
        return 0;
    }
    

    示例2——模板中的二义性:

    #include <iostream>
    
    using namespace std;
    
    int a = 0;
    
    class Test_1
    {
    public:
        static const int TS = 1;
    };
    
    class Test_2
    {
    public:
        struct TS
        {
            int value;
        };
    };
    
    template
    < class T >
    void test_class()
    {
        T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
                   // 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
    }
    
    int main(int argc, char *argv[])
    {
        // test_class<Test_1>();
        test_class<Test_2>();
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    test.cpp: In function ‘void test_class() [with T = Test_2]’:
    test.cpp:33:   instantiated from here
    test.cpp:26: error: dependent-name ‘T::TS’ is parsed as a non-type, but instantiation yields a type
    test.cpp:26: note: say ‘typename T::TS’ if a type is meant
    

    示例3——使用typename解决模板中的二义性:

    #include <iostream>
    
    using namespace std;
    
    int a = 0;
    
    class Test_1
    {
    public:
        static const int TS = 1;
    };
    
    class Test_2
    {
    public:
        struct TS
        {
            int value;
        };
    };
    
    template
    < class T >
    void test_class()
    {
        typename T::TS * a; // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
                            // 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
    }
    
    int main(int argc, char *argv[])
    {
        // test_class<Test_1>();
        test_class<Test_2>();
        
        return 0;
    }
    

    typename的作用:

    1. 在模板定义中声明泛指类型
    2. 明确告诉编译器其后的标识符为类型

    3.2 函数异常声明

    下面的程序想要表达什么意思?

    • try ... catch用于分隔正常功能代码与异常处理代码
    • try ... catch可以直接将函数实现分隔为2部分
    • 函数声明和定义时可以直接指定可能抛出的异常类型
    • 异常声明成为函数的一部分可以提高代码可读性

    函数异常声明的注意事项:

    • 函数异常声明是一种与编译器之间的契约
    • 函数声明异常后就只能抛出声明的异常
      1. 抛出其它异常将导致程序运行终止
      2. 可以直接通过异常声明定义无异常函数

    示例——新的异常写法:

    #include <iostream>
    
    using namespace std;
    
    int func(int i, int j) throw(int, char)
    {
        if( (0 < j) && (j < 10) )
        {
            return (i + j);
        }
        else
        {
            throw '0';
        }
    }
    
    void test(int i) try
    {
        cout << "func(i, i) = " << func(i, i) << endl;
    }
    catch(int i)
    {
        cout << "Exception: " << i << endl;
    }
    catch(...)
    {
        cout << "Exception..." << endl;
    }
    
    int main(int argc, char *argv[])
    {
        test(5);
        
        test(10);
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    func(i, i) = 10
    Exception...
    

    4.小结

    • C++中依然支持变参函数
    • 变参函数无法很好的处理对象参数
    • 利用函数模板变参函数能够判断指针变量
    • 构造函数和析构函数中不要抛出异常
    • class可以用来在模板中定义泛指类型(不推荐)
    • typename是可以消除模板中的二义性
    • try...catch 可以将函数体分成2部分
    • 异常声明能够提供程序的可读性
  • 相关阅读:
    MAC下MAMP安装php扩展教程记录
    公司通知正式上班邀请函 3分钟生成微信通知h5邀请函
    H5是什么?H5是否等于HTML5???
    浏览器渲染机制
    小程序后台布局,B站的经典写法
    原创:子组件传递数据给父组件
    父组件传递数据给自定义子组件过程
    原创: 自定义tabs切换组件并使用(微信小程序中
    原创: SpringBoot中filter的使用详解及原理
    若依官方文档 集成jwt实现登录授权访问,返回SysUser对象更多信息给前端
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10095963.html
Copyright © 2011-2022 走看看