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

摘要:
构造函数中抛出异常:构造过程立即停止当前对象无法生成析构函数不会被调用对象所占用的空间立即收回工程项目中的建议:不要在构造函数中抛出异常当构造函数可能产生异常时,使用二阶构造模式示例——构造中的异常:#includeusingnamespacestd;classTest{public:Test(){cout˂˂"Test()"˂˂endl;throw0;}virtual~Test(){cout˂˂"~Test()"˂˂endl;}};intmain{Test*p=reinterpret_cast;try{p=newTest();}catch(...){cout˂˂"Exception..."˂˂endl;}cout˂˂"p="˂˂

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

函数模板变参函数的化学反应:
C++解析(30):关于指针判别、构造异常和模板二义性的疑问第1张

示例——指针判断:

#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++解析(30):关于指针判别、构造异常和模板二义性的疑问第2张

历史上的原因:

  • 早期的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 函数异常声明

下面的程序想要表达什么意思?
C++解析(30):关于指针判别、构造异常和模板二义性的疑问第3张

  • 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部分
  • 异常声明能够提供程序的可读性

免责声明:文章转载自《C++解析(30):关于指针判别、构造异常和模板二义性的疑问》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【Docker】解析器指令之 escapeJava设计模式透析之 —— 适配器(Adapter)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Lua5.1中的API函数

Lua5.1中的API函数 lua_State*luaL_newstate()Lua脚本的编译执行是相互独立的,在不同的线程上执行。通过luaL_newstate()函数可以申请一个虚拟机,返回指针类型 lua_State。今后其他所有Lua Api函数的调用都需要此指针作为第一参数,用来指定某个虚拟机。lua_State* L = luaL_newst...

用 16G 内存存放 30亿数据(Java Map)转载

在讨论怎么去重,提出用 direct buffer 建 btree,想到应该有现成方案,于是找到一个好东西: MapDB - MapDB : http://www.mapdb.org/ 以下来自:kotek.net : http://kotek.net/blog/3G_map 3 billion items in Java Map with 16 GB R...

Delphi 关于指针Pointers 和@操作符

Delphi 关于指针Pointers 和@操作符 1、指针Pointers 指针是如何工作的,例如: 1 var 2 X, Y: Integer; // X和Y是整数变量 3 P: ^Integer; // P指向一个整数 4 begin 5 X := 17; // 给X赋值 6...

08 在设备树里描述platform_device【转】

转自:https://blog.csdn.net/jklinux/article/details/78575281 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/jklinux/article/details/78575281在设备树的dts文件...

but was actually of type 'com.sun.proxy.$Proxy101' 注入问题

最近在用springboot搭建项目框架时,遇到了如下错误,查询了一番,原来是没有引入spring框架的aop包导致: 问题: 1 ERROR o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springfra...

模型评估:模型状态评估

  模型状态 过拟合和欠拟合 过拟合:在训练集上的准确率较高,而在测试集上的准确率较低 欠拟合:在训练集和测试集上的准确率均较低 学习曲线(Learning Curves) 1)概念概述 学习曲线就是通过画出不同训练集大小时训练集和交叉验证的准确率,可以看到模型在新数据上的表现,进而来判断模型是否方差偏高或偏差过高,以及增大训练集是否可以减小过拟合。  ...