神奇的BOOST_MPL_ASSERT_MSG,神奇的解决方案,神奇的vc的bug

解决VC2005模板警告问题
本文探讨了解决VC2005环境下模板函数引发的警告及链接错误问题,通过修改BOOST_MPL_ASSERT_MSG宏定义,实现了跨文件编译时不产生重复定义错误,并保持静态成员函数的唯一性。

运行环境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.

### BOOST_MPL_CFG_COMPILER_DIR 未定义问题的解决方法 在使用 Boost.MPL 库时,若遇到 `BOOST_MPL_CFG_COMPILER_DIR` 宏未定义的问题,通常表明编译器未能自动识别当前环境并设置对应的预处理路径。Boost.MPL 依赖该宏来选择特定于当前编译器的优化头文件目录,例如 `boost/mpl/aux_/preprocessed/msvc/xxx.hpp` 或 `boost/mpl/aux_/preprocessed/gcc/xxx.hpp`。如果该宏未定义,可能导致预处理头文件路径拼接失败,进而引发编译错误[^1]。 #### 手动定义 `BOOST_MPL_CFG_COMPILER_DIR` 一种直接的解决方案是在构建命令中或项目配置中显式定义 `BOOST_MPL_CFG_COMPILER_DIR`。例如,在使用 MSVC 编译器时,可在编译命令前添加: ```bash -D BOOST_MPL_CFG_COMPILER_DIR=msvc ``` 或者在代码顶部手动定义该宏: ```cpp #define BOOST_MPL_CFG_COMPILER_DIR msvc #include <boost/mpl/list.hpp> ``` 这样可确保 Boost.MPL 使用正确的预处理路径,避免因宏未定义导致的头文件缺失问题[^1]。 #### 检查 Boost 构建配置 另一种可能的原因是 Boost 构建系统未能正确生成预处理配置。建议重新运行 Boost 构建脚本,并确保其检测到当前使用的编译器版本和平台特性。某些情况下,Boost 构建工具(如 `b2`)需要明确指定目标编译器类型,例如: ```bash ./b2 toolset=msvc ``` 这将确保生成的配置文件中包含对 `BOOST_MPL_CFG_COMPILER_DIR` 的正确支持。 #### 避免宏冲突与展开顺序问题 在宏拼接过程中,若涉及多个嵌套宏(如 `BOOST_MPL_PREPROCESSED_HEADER` 和 `BOOST_MPL_CFG_COMPILER_DIR`),需确保宏展开顺序正确。Boost 内部通常采用中间宏来强制展开,以避免路径字符串化前宏未被替换的问题: ```cpp #define INCLUDE_PATH_IMPL(dir, file) boost/dir/file #define INCLUDE_PATH(dir, file) INCLUDE_PATH_IMPL(dir, file) #include BOOST_PP_STRINGIZE(INCLUDE_PATH(BOOST_MPL_CFG_COMPILER_DIR, list_c.hpp)) ``` 此方式确保 `BOOST_MPL_CFG_COMPILER_DIR` 在被 `BOOST_PP_STRINGIZE` 处理前已展开为实际值,从而避免路径错误[^1]。 此外,部分第三方库(如 Windows SDK)可能会定义 `and`、`or` 等关键字宏,干扰 Boost.MPL 的宏展开逻辑。此时应使用如下方式临时取消这些宏定义: ```cpp #ifdef and #pragma push_macro("and") #undef and #endif #include BOOST_PP_STRINGIZE(boost/mpl/list.hpp) #ifdef and #pragma pop_macro("and") #endif ``` 这种方式可防止宏污染,但需注意不同编译器对 `#pragma push_macro` 的支持情况[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值