C++解析(30):关于指针判别、构造异常和模板二义性的疑问
0.目录
1.指针的判别
2.构造中的异常
- 2.1 如果构造函数中抛出异常会发生什么?
 - 2.2 如果析构函数中抛出异常会发生什么?
 
3.令人迷惑的写法
4.小结
1.指针的判别
面试问题:
编写程序判断一个变量是不是指针。
指针的判别:
拾遗:
- C++中仍然支持C语言中的可变参数函数
 - C++编译器的匹配调用优先级
- 重载函数
 - 函数模板
 - 变参函数
 
 
示例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 非指针
 - 编写函数:
- 指针变量调用时返回true
 - 非指针变量调用时返回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 如果析构函数中抛出异常会发生什么?
析构中的异常:
- 避免在析构函数中抛出异常!!
 - 析构函数的异常将导致:
- 对象所使用的资源无法完全释放。
 
 
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的作用:
- 在模板定义中声明泛指类型
 - 明确告诉编译器其后的标识符为类型
 
3.2 函数异常声明
下面的程序想要表达什么意思?

- try ... catch用于分隔正常功能代码与异常处理代码
 - try ... catch可以直接将函数实现分隔为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):关于指针判别、构造异常和模板二义性的疑问的更多相关文章
- 异常处理与MiniDump详解(2)  智能指针与C++异常
		
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie 讨论新闻组及文件 一. 综述 <异常处理与MiniDump详解(1) C++异常>稍 ...
 - 数据结构图文解析之:栈的简介及C++模板实现
		
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
 - 数据结构图文解析之:队列详解与C++模板实现
		
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
 - Flask框架(二)—— 反向解析、配置信息、路由系统、模板、请求响应、闪现、session
		
Flask框架(二)—— 反向解析.配置信息.路由系统.模板.请求响应.闪现.session 目录 反向解析.配置信息.路由系统.模板.请求响应.闪现.session 一.反向解析 1.什么是反向解析 ...
 - Delphi之通过代码示例学习XML解析、StringReplace的用法(异常控制 good)
		
*Delphi之通过代码示例学习XML解析.StringReplace的用法 这个程序可以用于解析任何合法的XML字符串. 首先是看一下程序的运行效果: 以解析这样一个XML的字符串为例: <? ...
 - HashMap 源码解析(一)之使用、构造以及计算容量
		
目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...
 - DRF框架(二)——解析模块(parsers)、异常模块(exception_handler)、响应模块(Response)、三大序列化组件介绍、Serializer组件(序列化与反序列化使用)
		
解析模块 为什么要配置解析模块 1)drf给我们提供了多种解析数据包方式的解析类 form-data/urlencoded/json 2)我们可以通过配置来控制前台提交的哪些格式的数据后台在解析,哪些 ...
 - C++解析(27):数组、智能指针与单例类模板
		
0.目录 1.数组类模板 1.1 类模板高效率求和 1.2 数组类模板 1.3 堆数组类模板 2.智能指针类模板 2.1 使用智能指针 2.2 智能指针类模板 3.单例类模板 3.1 实现单例模式 3 ...
 - C++学习笔记30,指针的引用(2)
		
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/guang_jing/article/details/32910093 能够创建不论什么类型的引用,包 ...
 
随机推荐
- [2016北京集训测试赛3]masodik-[凸包]
			
Description Soluton 666这道题竟然用凸包... 维护r和c的下凸壳.哪个斜率大走哪个. 证明:我们先不考虑其他的,只考虑两条路,如下图: 设图的长度为x,宽度为y.如果我们要走上 ...
 - 北京Uber优步司机奖励政策(4月17日)
			
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
 - 成都Uber优步司机奖励政策(4月14日)
			
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
 - PHP5.4 连接  SQL SERVER 2008
			
PHP链接sqlserver需要先安装驱动,不是先把dll放到ext下面,一重启服务器就完事了. 本地环境: XAMPP 1.8.2 PHP 5.4.31 SQL SERVER 2008 R2 使用的 ...
 - zedboard学习(1)OLED驱动显示图像
			
1. 干点啥?驱动一下上面的屏吧 2. 找个代码研究一下,cat命令用于读取文件(普通文件或设备文件)的内容并进行输出.据说板子已经做好OLED的驱动了,驱动映射为/dev/zed_oled,所以直接 ...
 - Fiddler 调用java webserivces
			
这是java写的webservice,并发布成功. 使用Fidder Get调用和POST调用 get比较简单: http://192.168.3.176:8080/AppTestService/se ...
 - 宿主机 PL/SQL Developer 连接虚拟机 ORACLE 数据库
			
1.确保主机与虚拟机间通信正常,双方关闭window防火墙.如能 ping 通,请确保两机IP在一个网段 2.主机安装orcl客户端 3.虚拟机 D:\app\lin\product\11.2.0\d ...
 - Egret入门(二)--windows下环境搭建
			
准备材料 安装Node.js TypeScript编辑器 HTTP服务器(可选) Chorme(可选) Egret 安装Node.js 打开www.nodejs.org 下载安装(全部next,全默认 ...
 - Valgrind 简单用法
			
有时需要给自己写的小程序做个简单的 benchmark,查看内存使用情况和运行时间.这时可以试试 valgrind. Ubuntu 下安装很简单: sudo apt-get update sudo a ...
 - app结合unity3D程序中遇到的问题 MapFileParser unity3d导出到IOS程序下 集成unity3dAR功能
			
转载自: 来自AR学院(www.arvrschool.com),原文地址为:http://www.arvrschool.com/index.php?c=post&a=modify&ti ...