使用调试断言时避免 ODR 冲突
Avoiding ODR violations when using debug asserts
我有一个仅标头库,在调试模式下编译时启用了一些额外的快速故障运行时断言。标头的简化版本如下所示:
#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;
}
};
如果一个翻译单元包含标头而不先定义MYDEBUG
,而另一个翻译单元在定义MYDEBUG
后包含它,并且我将生成的对象文件链接在一起,这是否构成 ODR 违规?
如何避免这种情况,但仍允许每个 TU 在包含标头时独立指定其所需的断言设置?
如果一个翻译单元在没有先定义
MYDEBUG
的情况下包含标头,而另一个翻译单元在定义MYDEBUG
后包含它,并且我将生成的对象文件链接在一起,这是否构成 ODR 违规?
是的,这违反了单一定义规则。这违反了内联函数的规则,该规则规定内联函数定义必须在所有翻译单元中具有确切的标记。
如何避免这种情况,但仍允许每个 TU 在包含标头时独立指定其所需的断言设置?
一种处理方法是将
MYASSERT
定义为文件范围的函数static
。
#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;
}
};
}
这实质上改变了内部链接Checker
,并可能带来额外的成本,也就是说,它最终可能会多次出现在最终的可执行文件中。但是,在这种特殊情况下,没有额外的成本,因为它无论如何都可能会内联。
解决方案 2:在调试模式下参数化模板:
(更新3:由于@Jarod42的建议,使用模板专用化(
#ifdef MYDEBUG
# define MYASSERT(condition) do{ if (!(condition)) std::terminate(); } while(0)
# define MYDEBUG_FLAG true
#else
# define MYASSERT(condition)
# define MYDEBUG_FLAG false
#endif
template<typename T, bool = MYDEBUG_FLAG> class Checker;
template<typename T>
class Checker<T, MYDEBUG_FLAG>
{
public:
T operator()(T value)
{
MYASSERT(value);
return value;
}
};
然后,调试和非调试实例化将彼此独立。
这样做的好处是,即使不小心实例化了Checker<T, !MYDEBUG_FLAG>
,它也不会编译,因此不会违反 ODR(前提是每个 TU 中只定义了一个版本,无论是调试版本还是非调试版本(。
第一个 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;
}
};
- 写入位置0x0000000C时发生访问冲突
- GL_SHADERSTORAGE_BUFFER位置是否与其他着色器位置冲突
- 使用cmake从源代码构建MySQL连接器/C++失败(与以前的声明冲突)
- 引发异常:读取访问冲突**dynamicArray**为0x1118235.发生
- C++LinkedList问题.数据类型之间存在冲突?没有匹配的构造函数
- 链表中写入访问冲突的未知原因
- C++中的openCV Mat访问冲突
- 如何使 std::sort 在 std::swap 和我的命名空间的模板化交换之间没有名称冲突?
- C++尝试深度复制唯一指针时出现内存访问冲突
- 错误:使用通用引用的声明冲突
- 如何解决GTest和LibTorch联动冲突
- 两个运营商的一些奇怪的冲突<<
- 如何在多个线程中创建 QSql数据库连接时防止名称冲突
- C++ 中动态二维数组的访问冲突
- 从嵌套循环中的 std::list 中删除将返回访问冲突
- C++17 十六进制浮点文字单精度后缀冲突?
- 结构字段名称与 GDB 中的 STL 数组冲突
- 写入访问冲突异常
- GCC 6.3.0 中的 ODR 冲突,类型在两个单独的翻译单元中定义
- 使用调试断言时避免 ODR 冲突