一、void类型
本文提到的void包括void及void* 两大类型,重点是void*。void的英文表示的空的,啥都没有的意思。那么在C/C++中基本也是这个意思。不过void*表示的是数据的指针类型啥都没有,而不是表示没有数据。那么没有数据类型是啥类型呢?就是开发者想把它看成什么类型就是什么类型。在C语言中可以直接强制转换到指定的数据类型而C++中则需要进行显示的转换(可参看前面的“数据类型转换的思考”)。
那有人会问,真是想怎么干就怎么干么?答案是真的。不过,但是,然尔…。只要你随心所欲不讲套路的乱干,干是没有问题的,但结果可能有大问题,比如直接Crash。所以还是不要放飞自我,至于不小心做错了,没事儿,事实会给你真实的反馈!
二、void的应用
在C/C++语言中,单纯使用void关键字,有几种使用情况:
1、函数的返回值:表示函数无返回值
2、参数的显式控制:表示函数的参数列表为空或没有参数
3、模板编程中进行一些特殊处理:这是一种比较重要的应用,就是模板编程的某些地方表示占位等的标记(可结合前面的std::void_t)
不过,需要提醒的是不能直接以void来定义变量,一定要注意!
而如果使用void* 则表示相关的指针数据是一种无类型的数据,在使用前需要转换到合适的数据类型。void*类型可能更象是早期的C/C++对数据处理的兼容性的一种妥协,毕竟很多的应用需要一种数据任意转换的支持,但囿于当时的情况或兼容性无法实现。常见的就是向线程内传递参数,一般都是void*。早期的开发者向线程传递数据都是如此,也没有什么稀奇的。
三、void*的转换和隐患
在C++编程中,void*数据的转换可以使用两种方法:
1、static_cast
2、reinterpret_cast
或者干脆使用C风格的强制转换。但不管是上面的C++风格的转换还是C风格的强制处理,都可能包含着隐含的风险。特别是在某些情况下,虽然知道原来的数据类型但为了简便想直接将其操作为另外一种类型时,风险的因素会增加的更多。举一个例子,把一个int指针类型的数据转换为void*后再将void*直接转成byte*类型时,可能就会出现大小容量处理的问题。
一般来说,void*的转换有两种情况,一种是把数据转成void*,一种是把void*的数据转成具体的数据类型。对于前者相对是安全的,而后者则有可能出现各种意外。包括但不限于数据类型的不匹配,内存对齐,大小端等等。究其原因,就是编译器不对这种转换的安全负责,它不会检查数据类型的是否匹配。这也是前面提到的,如果胡乱的转换,非常有可能崩溃的原因。
四、如何安全使用void*类型
对于void*的数据处理有以下几个建议:
1、尽量不做void*数据的处理,不制造问题就不会产生问题
2、尽量不使用C类型的强制转换而使用C++的方式,容易把控
3、尽量不要连续应用void*的数据类型转换(如上面的一步到位从int*转成byte*),而在要转换成原数据类型后再统一进行处理
4、关注一些细节,如大小端和字节对齐等
原则是能不用则不用,必须用的情况下确保数据安全和细节把控。
五、代码示例
看一下简单的代码说明:
//模板编程应用:在线程相关的文章中有详细的应用说明
#include <type_traits>
template<typename T, typename = void>
struct has_value_type : std::false_type {};
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};
static_assert(has_value_type<std::vector<int>>::value, "need type!");
再看一个线程的:
void* thFuncn(void* arg) {
//int* pars = static_cast<int*>(arg);
int* pars = (int*)arg;
std::cout << "Thread get pars is:" << *pars << std::endl;
......//work
std::cout << "Thread is end." << std::endl;
return NULL;
}
int main() {
pthread_t thread;
int arg =0;
int result = pthread_create(&thread, NULL, thFunc, &arg);
if (result != 0) {
std::cerr << " creating thread err! " << " error code: " << result << std::endl;
return 1;
}
pthread_join(thread, NULL);
std::cout << "thread quit." << std::endl;
return 0;
}
void*这种应用很简单,但需要注意的地方较多。在遇到诸如这种数据类型转换时,只要加强注意一般不会有什么问题,大家也不必过于担心。
六、总结
老生常谈,其实就是一个原则。在合适的场景下应用合适的方法,不教条不僵化。尽量减少使用不是强制不使用,难免有各种场景下不得不用,那么就需要认真的处理即可。有隐患不是说就一定会出现,只要把握内部的机理,就不会出现问题。