【问题标题】:COleDateTime::SetDateTime and noexcept (code analysis)COleDateTime::SetDateTime 和 noexcept(代码分析)
【发布时间】:2021-12-04 11:31:32
【问题描述】:

查看此代码:

COleDateTime CSpecialEventDlg::GetSpecialEventDate() noexcept
{
    COleDateTime    datEvent;

    if (datEvent.SetDateTime(m_datEvent.GetYear(),
            m_datEvent.GetMonth(),
            m_datEvent.GetDay(),
            0, 0, 0) != 0)
    {
        // Error

    }

    return datEvent;
}

我的代码分析表明我可以添加noexcept(我做了)。但我决定再调查一下。我注意到SetDateTime 返回一个值:

Zero 如果此COleDateTime 对象的值设置成功;否则,1。此返回值基于DateTimeStatus 枚举类型。有关详细信息,请参阅SetStatus 成员函数。

对于后一个函数 (SetStatus),它声明:

status 参数值由DateTimeStatus 枚举类型定义,该枚举类型在COleDateTime 类中定义。详情请见COleDateTime::GetStatus

现在,GetStatus 的文档记录了以下定义:

DateTimeStatus GetStatus() const throw();

因此,如果出现错误,它会引发异常。因此,我决定查看SetDateTime的MFC源码:

inline int COleDateTime::SetDateTime(
    _In_ int nYear,
    _In_ int nMonth,
    _In_ int nDay,
    _In_ int nHour,
    _In_ int nMin,
    _In_ int nSec) throw()
{
    SYSTEMTIME st;
    ::ZeroMemory(&st, sizeof(SYSTEMTIME));

    st.wYear = WORD(nYear);
    st.wMonth = WORD(nMonth);
    st.wDay = WORD(nDay);
    st.wHour = WORD(nHour);
    st.wMinute = WORD(nMin);
    st.wSecond = WORD(nSec);

    m_status = ConvertSystemTimeToVariantTime(st) ? valid : invalid;
    return m_status;
}

它使用ConvertSystemTimeToVariantTime 来设置状态。 那个用途:

inline BOOL COleDateTime::ConvertSystemTimeToVariantTime(_In_ const SYSTEMTIME& systimeSrc)
{
    return AtlConvertSystemTimeToVariantTime(systimeSrc,&m_dt);
}

那个使用:

inline BOOL AtlConvertSystemTimeToVariantTime(
    _In_ const SYSTEMTIME& systimeSrc,
    _Out_ double* pVarDtTm)
{
    ATLENSURE(pVarDtTm!=NULL);
    //Convert using ::SystemTimeToVariantTime and store the result in pVarDtTm then
    //convert variant time back to system time and compare to original system time.
    BOOL ok = ::SystemTimeToVariantTime(const_cast<SYSTEMTIME*>(&systimeSrc), pVarDtTm);
    SYSTEMTIME sysTime;
    ::ZeroMemory(&sysTime, sizeof(SYSTEMTIME));

    ok = ok && ::VariantTimeToSystemTime(*pVarDtTm, &sysTime);
    ok = ok && (systimeSrc.wYear == sysTime.wYear &&
            systimeSrc.wMonth == sysTime.wMonth &&
            systimeSrc.wDay == sysTime.wDay &&
            systimeSrc.wHour == sysTime.wHour &&
            systimeSrc.wMinute == sysTime.wMinute &&
            systimeSrc.wSecond == sysTime.wSecond);

    return ok;
}

此时我迷路了。简而言之,我假设SetDateTime 不会抛出异常。


我明白这一点,如果我决定在if 子句中调用GetStatus,那么我们确实有可能引发异常。

【问题讨论】:

  • 我没有留下我原来的一堆 cmets,而是决定将它们合并成一个答案。不确定它是否完全解决了您的疑虑,但它可能会在某种程度上消除您的疑虑。
  • 嗨 Andrew,我前段时间放弃了使用 COleDateTime。它仍然在我的一些代码中,但我有一个日期类,可以在我替换它时吃掉 COleDateTime。对我来说最重要的是他们使用双倍!??? Leap years?? the paste 如果你愿意。您现在可以访问所有 boost::gregorian 的好东西!
  • @lakeweb 多年来,我在我的项目中大量使用了COleDateTime,并针对波兰语和马耳他语等语言进行了一些微调。所以我不愿意改变我现在使用的类。但是感谢您的链接。

标签: visual-c++ mfc code-analysis noexcept coledatetime


【解决方案1】:

根据它们的声明,你已经展示了源代码的所有三个 MFC 函数(COleDateTime::SetDateTimeCOleDateTime::ConvertSystemTimeToVariantTimeAtlConvertSystemTimeToVariantTime)都有可能引发异常(因为它们没有相反的规范,如noexcept)。

但是,这并不意味着他们会(甚至可能会)。深入挖掘 MFC 源代码,在这三个函数中,我能看到的唯一可能引发异常的地方是 ATLENSURE(pVarDtTm!=NULL); 行(在第三个函数中)。

ATLENSURE宏定义如下:

#define ATLENSURE(expr) ATLENSURE_THROW(expr, E_FAIL)

ATLENSURE_THROW又是这样定义的:

#define ATLENSURE_THROW(expr, hr)          \
do {                                       \
    int __atl_condVal=!!(expr);            \
    ATLASSUME(__atl_condVal);              \
    if(!(__atl_condVal)) AtlThrow(hr);     \
} __pragma(warning(suppress:4127)) while (0)

因此,在您的代码中,如果expr(在上面的 sn-ps 中)为空(双键,!! 伪运算符使任何非零),似乎将引发异常值到1 并留下零作为零)。 exprpVarDtTm!=NULL 表达式的结果,它只能是 0 (false) 如果您的第二个 MFC 源代码摘录中调用中的 &amp;m_dt 参数是本身 NULL – 作为调用它的类对象成员的地址,这似乎不太可能(如果不是不可能)。


您遇到的另一个问题是您似乎误解了 DateTimeStatus GetStatus() const throw(); 声明中的 throw() 规范的含义。作为described here,它实际上是(自C++17 起)noexcept(或noexcept(true),更准确地说)的别名。要指定函数可以抛出 任何 类型的异常,应使用 throw(...)noexcept(false) 规范(或根本不使用异常/抛出规范)。

所以,你的最后一句话不是真的:

我明白这一点,如果我决定在if 子句中调用GetStatus,那么我们确实有可能出现异常 扔了。

因为GetStatus() 函数被明确指定为非抛出,并且您已经调用了SetDateTime 成员函数,它(如上所述) 可以抛出异常(但不会,在你的代码中)。

【讨论】:

  • 谢谢。所以对于我的代码,我应该删除 noexcept 关键字然后......?听起来像。
  • @Andrew 从我对 MFC 源代码和调用链的理解来看,离开它似乎是安全的。您的代码分析器似乎同意我的看法。
  • 把它留在里面,把它留在里面?
  • 是的。使用noexcept 规范。在调用该函数期间引发异常的极不可能事件中,您的程序将调用std::terminate(假设为 C++17 或更高版本的标准)。此外,除非您对GetSpecialEventDate 函数的每个 调用都包含在try...catch 块中,否则无论如何程序都会崩溃和烧毁。
猜你喜欢
  • 2013-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-31
  • 1970-01-01
  • 2010-12-05
  • 1970-01-01
相关资源
最近更新 更多