一:背景需求
在自定义数据类型的时候,需要在boolean上下文中判断该数据类型是否有意义(这个是否由意义不是我们所谓的是否为空等,而是根据这个数据类型具体的实现来体现)。例如:boost库中的shared_ptr指针,就可以直接在boolean上下文中判断该对象保存的资源指针是否有效,如果无效,在其中就会返回"false",如果有效就会返回"true".可能看过boost库源代码的朋友都知道,shared_ptr的实现,并不是简单的重载了我们的bool类型,进行一个类型转化,其实里面包含了比较复杂的思想,其中个别“滋味”需要慢慢咀嚼,我也是在学习中,写一些自己的心得!那么对于自定义数据类型,在必要的时候,例如 if(obj){},那么我们就需要自己定义转化操作符了。
二:需要知道的一些事情
在C++标准中,你知道有哪些数据类型是可以Implicit转换为bool类型的数据?
在查了相关的资料(可能不是很完整,欢迎大家补充),了解到对于内置的数据类型(整形、浮点型、枚举类型),返回右值的算法,一般指针、函数指针或者是成员指针(指向类型数据成员的指针或者是指向类型成员函数的指针)都可以在boolean上下文中转化为bool类型。<非0值为true,0值为false>,因此就是因为存在这些隐式转换,那么就会有多种方法实现相关的自定义类型的bool转换。
例如:
pointer p = new class;
if(p)
{}
if (some_type* p=get_some_type()) {
// p is valid, use it
}
else {
// p is not valid, take proper action
}
当然,上面的方法不仅仅只是针对内置类型有效,对于任何有明确意义的类型<完整类型>都可以使用这个默认的bool类型转换。对于没有明确意义的类型<譬如模板类>,这个时候它一般会提供相关的接口函数给你进行判断,例如
templateType<some_type> p(get_some_type());
if (p.is_valid()) {
// p is valid, use it
}
else {
// p is not valid, take proper action
}
与上面的默认转化做比较我们可以看出,除了代码量大之外,变量p还必须在判断的时候进行定义和声明,这样对于代码的维护不是很好。同时也不支持下面的泛型代码
template <typename T> void some_func(const T& t) {
if (t)
t->print();
}
现在问题比较明确了:对于自定义类型,如果提供相应的方法判断是否有效,那么对于很多泛型算法可能无法适应,那么我们必须要自定义自己的bool类型转换操作符,或者是转化为上面提到的相关类型,然后再隐式转化为bool。
只有正确的认识问题,才可能找到方法去解决问题。
三:实现方法
(1)最直接了当的方法,重载bool强制操作符
举例如下:
class Testable {
bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}
operator bool() const {
return ok_;
}
};
自定义类型Testable,然后定义了bool转换操作符,通过返回成员变量ok_来确定该class的object是否由意义,这个时候的意义不是判断该对象是否存在。
对于如下代码可能会很好的满足
Testable test1;
if( test1 )
{}
else
{}
对泛型算法是否也可以比较好的支持,但是还是有问题存在
例如:
Testable test1;
int a = test1; // 对象转化为bool类型,然后bool类型转化为int类型<通过隐式转化>
上面似乎就不是我们所期待的事情发生了,对象赋值给一个int变量。但是这个在C++中确实存在。
也可能存在下面比较让人不可理解的事情:
Testable a(true);
Testable b(false);
if ( a== b)
{}
else
{}
本来自己没有实现先关的operator==()操作符,但是不同的对象也可以进行==操作符的比较,其实是编译器进行了默认转化,把两个对象转化为了bool类型,然后bool类型进行相互比较,但是这种比较是毫无意义。
那么如何改进这种方法呢?
可以自己在定义一个private的operator int()操作符,来阻止向int型的转化,但是这会扰乱相关的代码维护人员,导致不必要的复杂语义,可能还会有一些情况产生。
所以在很多时候,我们对自定义数据类型在boolean上下文中的bool类型转换操作一般都不会以这种方式去实现,一般最容易想到的方法,也不会是好方法。
方法二:重载void*操作符,返回一个指针变量,对指针进行判断
在前面的讨论中已经说明了,在C++标准中指针变量是可以默认转化为bool类型的变量,适合在boolean上下文中进行判断。
例如
int *p = new int;
if ( p)
{}
else
{}
在C++中void*的指针在boolean上下文中也可以转化为bool值进行判断,可能有些人有疑问,为什么不能转化为其它类型的指针呢?因为只有void*的指针是最合适的,其它类型的指针都有自己明确的意义。
operator void*() const {
return ok_==true ? this : 0;
}
但是对于这种转换,最让人不可思议的是:
Testable a;
delete a; // 能够正确的编译,不过通常执行的时候会崩溃。
查过一些资料,发现对于这种方法的使用在I/0流的库中存在,在boolean上下文中,通过查询流的状态,判断是否正常。
具体的代码在:
头文件:#include<xiobase>
代码:
operator void *() const
{ // test if any stream operation has failed
return (fail() ? 0 : (void *)this);
}
bool __CLR_OR_THIS_CALL operator!() const
{ // test if no stream operation has failed
return (fail());
}
在具体的代码中可以这样使用
if( std::cin)
{
std::cout << std::cin << std::cout;
}
虽然在标准库中存在该种方法的使用,但是在用户自定义类型中,使用这种方法进行定义也不是很好,不过如果个人感觉比较安全,也可以利用该种方法实现。
第三种方法:重载operator!()操作符
就上面的I/O库中使用的方法一样,我们可以重载operator!(),然后再需要boolean上下文中的地方使用方法来满足。
具体实现如下:
class Testable {
bool not_ok_;
public:
explicit Testable(bool b=true):not_ok_(!b) {}
bool operator!() const {
return not_ok_;
}
};
使用代码如下:
Testable test;
if ( !test )
{}
else
{}
在汉语中就有“双重否定表示肯定”的意味,如果要标识是否有效,那么还需要使用if(!!test),其实这样技术在以前比较早的版本库中也使用,当时还作为一种技术提出来了the double-bang trick,比如上面的I/O库中就用到了,这种方法在某种程度上说已经避免了前面提出的很多很多弊端,比如:默认转换为int类型,使用为定义的==操作符但是没有意义,可以使用delete运算符等。
不过也有些不是很优雅的地方:书写代码繁琐,语义不是很明确,最重要的是在某种程度上不能很好的满足泛型算法,值得借鉴,但是也值得考虑!
方法四:同样定义相关的指针转换操作重载,但是不是重载void*,而是重载一个内嵌的自定义类型的指针类型
这个是在相关的资料上查到的此种方法
实现代码可能如下:
class Testable {
bool ok_;
public:
explicit Testable(bool b=true):ok_(b) {}
class nested_class;
operator const nested_class*() const {
return ok_ ? reinterpret_cast<const nested_class*>(this) : 0;
}
};
不过自己进过测试后,还是不能避免相关的delete操作符和如下无意义的代码
Testable b1,b2;
if (b1==b2) {
}
if (b1<b2) {
}
个人感觉这种方法,也不是很好。不过在delete编译的时候会有相关的警告,这点可能会提醒用户。
方法五:实现安全的bool类型转换
实现代码可能如下:
class Testable {
bool ok_;
typedef void (Testable::*bool_type)() const; // typedef 一个成员函数指针
void this_type_does_not_support_comparisons() const {} // 内部的const成员函数
public:
explicit Testable(bool b=true):ok_(b) {}
operator bool_type() const {
return ok_==true ?
&Testable::this_type_does_not_support_comparisons : 0;
}
};
这其实一种比较隐含的指针类型转化为bool值,实质是成员指针转化了bool值,这里面运用到了一个小注意点:delete运算符不能删除成员函数指针。那么这样就避免了delete操作符的误用,同时也实现了boolean上下文的转换,满足了相关的泛型算法!
不过还是无法避免如下无意义的代码:
Testable test;
Testable test2;
if (test1==test2) {}
if (test!=test2) {}
在某种程度上来说,上面的方法已经做的比较好了,基本上已经具备了上面四种方法的所有不足,如果你自定义的数据类型定了相关的==和!=操作符,那么这个没有问题,如果没有定义,那么可能就需要你自己定义一些相关的函数去避免这种不必要的或者是无意义的代码:
template <typename T>
bool operator!=(const Testable& lhs,const T& rhs) {
lhs.this_type_does_not_support_comparisons();
return false;
}
template <typename T>
bool operator==(const Testable& lhs,const T& rhs) {
lhs.this_type_does_not_support_comparisons();
return false;
}
可以在你自定义类型的作用域中定下相关的泛型操作符重载函数,如果一旦出现上面的代码调用,那么就会编译报错,因为无法调用私有函数嘛!
现在罗列在boost库中是如何安全重载bool的:
#if ( defined(__SUNPRO_CC) && BOOST_WORKAROUND(__SUNPRO_CC, < 0x570) ) || defined(__CINT__)
operator bool () const
{
return px != 0;
}
#elif defined( _MANAGED )
static void unspecified_bool( this_type*** )
{
}
typedef void (*unspecified_bool_type)( this_type*** );
operator unspecified_bool_type() const // never throws
{
return px == 0? 0: unspecified_bool;
}
#elif \
( defined(__MWERKS__) && BOOST_WORKAROUND(__MWERKS__, < 0x3200) ) || \
( defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ < 304) ) || \
( defined(__SUNPRO_CC) && BOOST_WORKAROUND(__SUNPRO_CC, <= 0x590) )
typedef T * (this_type::*unspecified_bool_type)() const;
operator unspecified_bool_type() const // never throws
{
return px == 0? 0: &this_type::get;
}
#else
typedef T * this_type::*unspecified_bool_type;
operator unspecified_bool_type() const // never throws
{
return px == 0? 0: &this_type::px;
}
#endif
// operator! is redundant, but some compilers need it
bool operator! () const // never throws
{
return px == 0;
}
{
return px != 0;
}
基本上相关的方法在这里中综合了,通过各种编译宏进行控制,因为现在在使用boost库,源码没有怎么研究,所以只在这里罗列,仅供参考。
上面的很多东西其实都是在看了一篇英文文档后,自己看过后,通过自己的语言把它翻译出来了!
文档上在最后,还提出了一种利用模板技术解决该种问题的方法,因为模板学习的不多,还没有好好的理解,连接贴出,有兴趣的可以自己看看!
参考文档:http://www.artima.com/cppsource/safebool3.html
整个总结做完,希望有好的方法在提出!
O(∩_∩)O!