使用调试断言时避免ODR违规

时间:2018-01-22 18:11:55

标签: c++ one-definition-rule

我有一个只有头的库,在调试模式下编译时会启用一些额外的失败快速运行时断言。标题的简化版本如下所示:

navigator.geolocation.getCurrentPosition(success);
function success(pos) {
  var crd = pos.coords;
  console.log('Your current position is:');
  console.log(`Latitude : ${crd.latitude}`);
  console.log(`Longitude: ${crd.longitude}`);
  console.log(`More or less ${crd.accuracy} meters.`);
  initMap(crd);
};

function initMap(coordinates) {
    var location = {'lng': coordinates.longitude, 'lat': coordinates.latitude};

    var map = new google.maps.Map(document.getElementById('map'), {
      zoom: 4,
      center: location
    });
    var marker = new google.maps.Marker({
      position: location,
      map: map
    });
}

如果一个翻译单元首先包含标题而没有定义#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之后包含它,并且我将结果对象文件链接在一起,是否会构成ODR违规?

如何避免这种情况,但仍然允许每个TU在包含标题时独立指定其所需的断言设置?

3 个答案:

答案 0 :(得分:3)

  

如果一个翻译单元首先包含标题而没有定义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 :(得分:2)

解决方案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&#39;的建议

#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中定义。

答案 2 :(得分:0)

第一个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;
    }
};