用户操作
[留言]  [发消息]  [加为好友] 
订阅我的博客
XML聚合    FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
lsaturn的公告
<iframe border="0" frameborder="no" framespacing="0" src="http://www.xise.cn/qita/rili/cal5.htm" scrolling="no" width="164" height="174"></iframe> <EMBED src="http://www.aswis.net/100000/clock/clock47.swf" type="application/x-shockwave-flash" width="180" height="180"></EMBED> <IFRAME style="WIDTH: 190px; HEIGHT: 210px" src="http://weather.news.qq.com/inc/ss166.htm" frameBorder=0 width=170 scrolling=no height=200></IFRAME>
文章分类
程序员兄弟
李马
存档

原创  神奇的BOOST_MPL_ASSERT_MSG,神奇的解决方案,神奇的vc的bug 收藏

运行环境VC2005.

工程里面写了这么一段代码,目的是为了实现DWORD<==>DWORD*之间完成转型而不报warning

template <typename Obj_T, typename Src_T>
Obj_T  union_cast(Src_T src)
{

    
// 编译期检查Src_T和Obj_T的数据大小是一致。
    
//
    BOOST_MPL_ASSERT_MSG(sizeof Obj_T == sizeof Src_T, obj_type_size_is_not_same_with_src_type, (Obj_T, Src_T));                                                         //因为利用了union的特性,所以要保证转型的变量大小要相等

    union
    
{
        Src_T m_src;
        Obj_T m_obj;
    }
 tmp_union;

    tmp_union.m_src 
= src;
    
return tmp_union.m_obj;
}

刚开始只在一个.cpp文件里面使用没有什么问题,但是在两个或以上.cpp(比如1.cpp和2.cpp)里面使用就出现问题了,会报如下联结错误:

1.obj : error LNK2005: "public: static struct boost::mpl::failed * * * * * * * * * * * * (__thiscall `unsigned long * __cdecl union_cast<unsigned long *,unsigned long>(unsigned long)'::`2'::obj_type_size_is_not_same_with_src_type::** * * * * * * * * * * __cdecl `unsigned long * __cdecl union_cast<unsigned long *,unsigned long>(unsigned long)'::`2'::obj_type_size_is_not_same_with_src_type15::assert_arg(void))(unsigned long *,unsigned long)" (?assert_arg@obj_type_size_is_not_same_with_src_type15@?1???$union_cast@PAKK@@YAPAKK@Z@SAPAPAPAPAPAPAPAPAPAPAPAP8obj_type_size_is_not_same_with_src_type@?1???$union_cast@PAKK@@YAPAKK@Z@AEPAPAPAPAPAPAPAPAPAPAPAPAUfailed@mpl@boost@@PAKK@ZXZ) already defined in 2.obj

看着比较恐怖,但是实际上就是说union_cast里面assert_arg函数被重复定义了,奇怪了,我又没有定义过assert_arg这个函数,那肯定是BOOST_MPL_ASSERT_MSG这个宏干的事情了(这个宏实际上完成的工作是,编译期的assert),查看下这个宏的定义,是这么写的:

#   define BOOST_MPL_ASSERT_MSG( c, msg, types_ ) 
    
struct msg; 
    typedef 
struct BOOST_PP_CAT(msg,__LINE__) : boost::mpl::assert_ 
    

        
static boost::mpl::failed ************ (msg::************ assert_arg()) types_ 
        
return 0; } 
    }
 BOOST_PP_CAT(mpl_assert_arg,__LINE__); 
    
enum 
        BOOST_PP_CAT(mpl_assertion_in_line_,__LINE__) 
= sizeof
            boost::mpl::assertion_failed
<(c)>( BOOST_PP_CAT(mpl_assert_arg,__LINE__)::assert_arg() ) 
            ) 
    }

哈,凶手出来了,宏展开之后会定义一个函数

static boost::mpl::failed ************ (msg::************ assert_arg()) types_
        
return 0; } 

不要被这个函数吓倒,我把它分开来写,或许你就知道了:

typedef boost::mpl::failed ************ (msg::************ _ret) types_

static _ret assert_arg()return 0; }

虽然有那么多*,但是不过就是个函数指针,*不过是为了出错信息更容易看出来,毕竟模板出错信息返回一大堆,return 0就是个NULL指针,其实后面继续研究会发现这个函数都不需要定义,只需要声明就可以了,此为后话.

因为模板的原因,必须把生命和定义都写在hpp里面,这样会导致1.obj和2.obj都会有一个相同的定义,所以会报最初的那个错误,尝试了下,这么写就不会报错:

//.h
void f();

//.cpp
void f()
{
    BOOST_MPL_ASSERT_MSG(
sizeof Obj_T == sizeof Src_T, obj_type_size_is_not_same_with_src_type, (Obj_T, Src_T));
}

但是模板不能够分开写,或许这样可以解决问题

namespace
{
    template 
<typename Obj_T, typename Src_T>
    Obj_T  union_cast(Src_T src)
    
{

        
// 编译期检查Src_T和Obj_T的数据大小是一致。
        
//
        BOOST_MPL_ASSERT_MSG(sizeof Obj_T == sizeof Src_T, obj_type_size_is_not_same_with_src_type, (Obj_T, Src_T));

        union
        
{
            Src_T m_src;
            Obj_T m_obj;
        }
 tmp_union;

        tmp_union.m_src 
= src;
        
return tmp_union.m_obj;
    }

}

bingo!!!!!!!解决了,注意要使用匿名名字空间哦,这样在1.obj和2.obj里面会生成完全不同的符号,因此不会发生冲突,如果使用具名的命名空间就又会冲突哦,对于匿名名字空间里面函数,对于cpp来说,可以无视命名空间,也就是说以前的使用代码完全不用更改,直接使用,完美的解决方案:)

====================================================================================

2007年3月9日,继续更新

原来以为是完美的解决方案,结果又出了新的问题,因为在不同的cpp里面的话,匿名namespace相当于不同的命名空间,所以在cpp里面会生成不同的代码,那么如果在union_cast里面出现static int x这样的变量也会产生两份,因此完全背离了定义static的意义.另外因为在匿名空间里面,没有办法把一个类的定义和声明分开来写,所以,不完美!!!!!!!那么继续深究,到底是什么导致编译冲突

//在同一个工程里面
//1.cpp
static void f()
{
    
class A
    
{
        
static void fff()
        
{
        }

    }
;
}


//2.cpp
static void f()
{
    
class A
    
{
        
static void fff()
        
{
        }

    }
;
}

结果编译的时候两个obj里面的fff()函数冲突了,难道是static的函数的问题,继续测试:

//1.cpp
static void f()
{
    
class A
    
{
        
void fff()
        
{
        }

    }
;
}


//2.cpp
static void f()
{
    
class A
    
{
        
void fff()
        
{
        }

    }
;
}

继续连接冲突,晕死

//1.cpp
class A
{
    
static void fff()
    
{
    }

}
;

//2.cpp
class A
{
    
static void fff()
    
{
    }

}
;

这样就不会冲突,我把上面两个会冲突的写法,用gcc来编译,就可以顺利编过,因此我认为这是一个vc的bug,事实我要接受,我也不可能转去gcc,来想想解决办法吧

转回来看BOOST_MPL_ASSERT_MSG的实现,这里再贴一次

#   define BOOST_MPL_ASSERT_MSG( c, msg, types_ ) 
    
struct msg; 
    typedef 
struct BOOST_PP_CAT(msg,__LINE__) : boost::mpl::assert_ 
    

        
static boost::mpl::failed ************ (msg::************ assert_arg()) types_ 
        
return 0; } 
    }
 BOOST_PP_CAT(mpl_assert_arg,__LINE__); 
    
enum 
        BOOST_PP_CAT(mpl_assertion_in_line_,__LINE__) 
= sizeof
            boost::mpl::assertion_failed
<(c)>( BOOST_PP_CAT(mpl_assert_arg,__LINE__)::assert_arg() ) 
            ) 
    }

下面这个

     enum 
        BOOST_PP_CAT(mpl_assertion_in_line_,__LINE__) 
= sizeof
            boost::mpl::assertion_failed
<(c)>( BOOST_PP_CAT(mpl_assert_arg,__LINE__)::assert_arg() ) 
            ) 

目的就是为了sizeof,sizeof不会执行函数,只关心函数的返回类型,所以assert_arg()有没有实现都是没有问题的,因此可以修改为

#   define BOOST_MPL_ASSERT_MSG( c, msg, types_ ) 
    
struct msg; 
    typedef 
struct BOOST_PP_CAT(msg,__LINE__) : boost::mpl::assert_ 
    

        
static boost::mpl::failed ************ (msg::************ assert_arg()) types_ ;
    }
 BOOST_PP_CAT(mpl_assert_arg,__LINE__); 
    
enum 
        BOOST_PP_CAT(mpl_assertion_in_line_,__LINE__) 
= sizeof
            boost::mpl::assertion_failed
<(c)>( BOOST_PP_CAT(mpl_assert_arg,__LINE__)::assert_arg() ) 
            ) 
    }

这样问题可以得到解决,但是会报一个warning:C4822 : local class member function does not have a body,还是不完美啊,继续.大家知道纯虚函数是不需要实现的,但是必须在对象上实现,但是纯虚的对象是无法定义的,灵光一闪,黄金万两,反正不会真正调用,只要语法过了就对了,那么我们可以在一个null指针上面调用,最终版本就像下面:

#   define BOOST_MPL_ASSERT_MSG( c, msg, types_ ) 
    
struct msg; 
    typedef 
struct BOOST_PP_CAT(msg,__LINE__) : boost::mpl::assert_ 
    

        
virtual boost::mpl::failed ************ (msg::************ assert_arg()) types_  = 0;
    }
 BOOST_PP_CAT(mpl_assert_arg,__LINE__); 
    
enum 
        BOOST_PP_CAT(mpl_assertion_in_line_,__LINE__) 
= sizeof
            boost::mpl::assertion_failed
<(c)>( BOOST_PP_CAT(static_cast<mpl_assert_arg,__LINE__)*>(0)->assert_arg() ) 
            ) 
    }

完美解决,这次发现了boost一个bug,vc2005(vc2003也有)一个bug.

发表于 @ 2007年03月08日 14:17:00 | 评论( loading... ) | 编辑| 举报| 收藏

旧一篇:深究跨dll的资源分配和释放问题 | 新一篇:如何通过崩溃地址找到出错的代码行

  • 发表评论
  • 评论内容:
  •  
Copyright © lsaturn
Powered by CSDN Blog