C++ 使用调试断言时避免ODR冲突
我有一个只有头的库,当在调试模式下编译时,它启用了一些额外的fail fast运行时断言。标题的简化版本如下所示:C++ 使用调试断言时避免ODR冲突,c++,one-definition-rule,C++,One Definition Rule,我有一个只有头的库,当在调试模式下编译时,它启用了一些额外的fail fast运行时断言。标题的简化版本如下所示: #include <exception> #ifdef MYDEBUG # define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0) #else # define MYASSERT(condition) #endif template<typename T
#include <exception>
#ifdef MYDEBUG
# define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0)
#else
# define MYASSERT(condition)
#endif
template<typename T>
class Checker
{
public:
T operator()(T value)
{
MYASSERT(value);
return value;
}
};
#包括
#ifdef MYDEBUG
#定义MYASSERT(条件)do{if(!(条件))std::terminate();}while(0)
#否则
#定义MYASSERT(条件)
#恩迪夫
模板
类检查器
{
公众:
T运算符()(T值)
{
MYASSERT(值);
返回值;
}
};
如果一个翻译单元包含标题,但没有首先定义MYDEBUG
,而另一个翻译单元在定义MYDEBUG
之后包含标题,并且我将生成的对象文件链接在一起,这会构成ODR冲突吗
如何避免这种情况,但仍然允许每个TU在包含标头时独立指定其所需的断言设置
如果一个翻译单元包含标题,但没有首先定义MYDEBUG
,而另一个翻译单元在定义MYDEBUG
之后包含标题,并且我将生成的对象文件链接在一起,这会构成ODR冲突吗
是的,这违反了一个定义规则。这违反了内联函数的规则,即内联函数定义必须在所有转换单元中具有精确的标记
如何避免这种情况,但仍然允许每个TU在包含标头时独立指定其所需的断言设置
一种处理方法是将MYASSERT
定义为文件范围的静态
函数
#ifdef MYDEBUG
static void MYASSERT(bool condition)
{
if (!(condition))
{
std::terminate();
}
}
#else
static void MYASSERT(bool condition)
{
// Noop
}
#endif
看来你不能。谢谢,@RustyX。解决方案1:使用范围界定:
#ifdef MYDEBUG
# define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0)
#else
# define MYASSERT(condition)
#endif
namespace {
template<typename T>
class Checker
{
public:
T operator()(T value)
{
MYASSERT(value);
return value;
}
};
}
然后,调试和非调试实例化将彼此独立
这样做的好处是,即使一个人意外地实例化了
检查器,它也不会编译,因此也不会违反ODR(只要每个TU中只定义了一个版本,无论是调试版本还是非调试版本)。first RustyX答案的变体,但我认为已修复:
#ifdef MYDEBUG
# define MYDEBUG_FLAG true
#else
# define MYDEBUG_FLAG false
#endif
#define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0)
// Following declaration differs, but doesn't break ODR.
template<typename T, bool = MYDEBUG_FLAG> class Checker;
// And both definitions of specialization.
template <typename T>
class Checker<T, true>
{
public:
T operator()(T value)
{
MYASSERT(value);
return value;
}
};
template <typename T>
class Checker<T, false>
{
public:
T operator()(T value)
{
return value;
}
};
#ifdef MYDEBUG
#定义MYDEBUG_标志true
#否则
#定义MYDEBUG_标志false
#恩迪夫
#定义MYASSERT(条件)do{if(!(条件))std::terminate();}while(0)
//以下声明有所不同,但不会破坏ODR。
模板类检查器;
//以及专业化的两个定义。
模板
类检查器
{
公众:
T运算符()(T值)
{
MYASSERT(值);
返回值;
}
};
模板
类检查器
{
公众:
T运算符()(T值)
{
返回值;
}
};
为什么不使用MYDEBUG
作为转换运算符的编译时参数T操作符()(T值){return do_conversion(value,MYDEBUG);}
,其中do_conversion
可能包含也可能不包含assertNope,仍然违反了。单一定义的思想是避免链接器必须折叠多个定义时出现问题,例如,如果函数是虚拟的或通过指针调用。实际上只剩下一个版本。一个带有外部链接的[…]内联函数,[…]非静态函数模板,[…]可以有多个定义,只要这些定义满足以下要求。
@sp2danny-当然。仍然违反ODR的是operator()
。我相信这段代码在技术上仍然违反ODR,因为模板参数的默认值不是定义的一部分。部分专门化应该可以解决这个问题,不是吗?@Jarod42,应该可以,但我不确定您打算如何选择它?@SergeyA:Like?(声明不同,但定义相同)。@Jarod42这很聪明,我看不出为什么它不起作用。想补充一下吗?即使OP不喜欢它,我也一定会投票。@RustyX:事实上,用户在混合MYDEBUG
标志(我认为这是一个阻塞(无法解决)问题)时,必须处理Checker
Checker
的混合。。。
#ifdef MYDEBUG
# define MYDEBUG_FLAG true
#else
# define MYDEBUG_FLAG false
#endif
#define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0)
// Following declaration differs, but doesn't break ODR.
template<typename T, bool = MYDEBUG_FLAG> class Checker;
// And both definitions of specialization.
template <typename T>
class Checker<T, true>
{
public:
T operator()(T value)
{
MYASSERT(value);
return value;
}
};
template <typename T>
class Checker<T, false>
{
public:
T operator()(T value)
{
return value;
}
};