【问题标题】:How to use std::transform without violating MISRA C++ 2008 Advisory Rule 5-2-10?如何在不违反 MISRA C++ 2008 咨询规则 5-2-10 的情况下使用 std::transform?
【发布时间】:2013-08-09 10:49:57
【问题描述】:

我在 PC-Lint (au-misra-cpp.lnt) 中收到这些错误:

ConverterUtil.cpp(90): 错误 864:(信息——涉及变量“transformValue”的表达式 可能取决于评估顺序 [MISRA C++ Rule 5-2-10])

ConverterUtil.cpp(90): 错误 864:(信息——涉及变量“transformValue”的表达式 可能取决于评估顺序 [MISRA C++ Rule 5-2-10])

ConverterUtil.cpp(90): 错误 534:(警告 -- 忽略函数的返回值 'std::transform(std::_String_iterator>>, std::_String_iterator>>, std::_String_iterator>>, int (*)(int))'(与第 998 行比较,文件 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\include\algorithm) [MISRA C++ 规则 0-1-7 和 8-4-6],[MISRA C++ 规则 0-3-2])

关于此代码:

/**Conversion from std::string to bool*/
bool ConverterUtil::ConvertStdStringToBool(const std::string value)
{
    std::string transformValue = value;
    bool retValue = false;

    std::transform(transformValue.begin(), transformValue.end(), transformValue.begin(), &::tolower);


    if(transformValue == std::string(static_cast<const char *>("true")))
    {
        retValue = true;
    }

    return retValue;
}

我猜它不喜欢我在转换中使用相同的 std::string 作为输入和输出,但使用另一个字符串作为输出会产生相同的错误。

是否可以使 std::transform MISRA 兼容?

【问题讨论】:

  • 不相关,但是因为您将 value 按值传递给您的函数,所以不需要 tranformValue 本地。您已经在value 中有一个临时副本。您可以放弃参数上的const 并在代码中使用value 而不是tranformValue(尽管我会建议保持原样,但将参数设为const-ref)。
  • 也许它希望你复制 begin()end() 迭代器,并将副本传递给 std::transform
  • 如果您只想要不区分大小写的比较,请在此处查看大量答案stackoverflow.com/questions/11635/…
  • @WhozCraig:在这种特殊情况下,按值传递比通过 ref-to-const 传递更好。见here。否则,我同意你所说的一切。
  • @CassioNeri 是的,我同意。这对我来说是一个一致性的事情,当它经常由 const-ref 完成时,很难打破这个习惯。同意。

标签: c++ misra


【解决方案1】:

我只是在这里猜测(如果它不能解决您的问题,我可能会删除答案)。

尝试将包含std::transform 的行替换为这两个:

auto dest = transformValue.begin();
std::transform(transformValue.cbegin(), transformValue.cend(), dest, &::tolower);

注意cbegin()cend() 的使用(而不是begin()end())。

关于另一个主题:您将传递给ConvertStdStringToBool 的字符串复制了两次,而您只能复制一次。为此,请替换:

bool ConverterUtil::ConvertStdStringToBool(const std::string value)
{
    std::string transformValue = value;

bool ConverterUtil::ConvertStdStringToBool(std::string transformValue)
{

(您可能希望在此更改后将 transformValue 重命名为 value)。

更新:我的解释为什么我认为它会有所帮助。

首先,请注意transformValue 不是const。因此,transformValue.begin()transformValue.end() 将调用这些重载:

iterator begin(); // non const overload
iterator end();   // non const overload

因此静态分析器(正确地)得出结论,begin()end() 可能会改变transformValue 的状态。在这种情况下,transformValue 的最终状态可能取决于首先调用 begin()end() 中的哪一个。

现在,当您调用 cbegin()cend() 时,重载如下:

const_iterator cbegin() const; // notice the const 
const_iterator cend() const;   // notice the const

在这种情况下,静态分析器不会推断出这些调用会改变transformValue 的状态并且不会引发问题。 (严格来说,即使方法是const,它们也可以更改状态,因为类中可能存在mutable 数据成员,或者这些方法可能使用邪恶的const_cast。恕我直言,不应该责怪静态分析器那个。)

最后一句话:电话

std::transform(transformValue.cbegin(), transformValue.cend(), transformValue.cbegin(), &::tolower);
                                                                              ^^^^^^

错了。第三个参数必须是非const迭代器,也就是说,它必须transformValue.begin()(只有前两个参数是c*方法)。

但是,我想,出于与上述类似的原因,仅使用 transformValue.begin() 作为第三个参数是不够的,这就是我建议创建另一个变量 (dest) 的原因。

【讨论】:

  • 使用 begin() end() 和 cbegin() 和 cbegin() cend() 有什么区别?因为这就是让它快乐的全部:std::transform(transformValue.cbegin(), transformValue.cend(), transformValue.cbegin(), &::tolower);
  • @MathiasWestin cbegin() 返回const_iterator。坦率地说,我很震惊你的输出迭代器作为一个 const-iterator 甚至 编译。你确定你的代码中不只是begin()(第三个参数)吗?
  • @WhozCraig 你是对的,它没有编译,我很高兴 PCLint 没有抛出任何错误! :) 所以它必须是您在上面示例中显示的方式。
【解决方案2】:

这不是一个单独的答案,更像是对卡西欧答案的评论,但是评论太长了。

以下是直接使用transformValue.begin() 作为第三个参数实际上很容易失败的方法:在 C++03 中(不是 11,但 GCC 到目前为止还没有切换),std::string 的引用计数实现是允许。 libstdc++ 有一个。使用这样的版本,valuetransformValue 将共享它们的内部缓冲区。

现在,当调用transformValue.begin() 时,生成的非常量迭代器可用于修改缓冲区,这很糟糕,因为它也会更改value。所以begin() 必须取消共享缓冲区,即为transformValue 分配一个唯一的缓冲区。这样做会使所有现有迭代器失效

因此,在C++98版本中调用cbegincend

const std::string& constValue = transformValue;
std::transform(constValue.begin(), constValue.end(),
               transformValue.begin(), &::tolower);

你有一个真正的订单依赖。如果在 const 调用之前调用了 begin() 的非 const 版本,那么一切都很好。但是如果首先调用 const 版本(或end()),新返回的迭代器将被非常量调用无效,从而产生未定义的行为。

令人讨厌的是,代码可能仍然可以工作,因为无效的迭代器将指向旧缓冲区,该缓冲区由value 保持活动状态。由于在发生这种取消共享的大多数情况下,旧副本将比新副本更长,这是一个非常讨厌的休眠错误,直到两者都不会出现

  • 旧副本可能会在操作过程中消失,例如因为旧副本已经消失,但另一个副本存在于另一个线程中并且可能随时消失,或者
  • 代码的更改方式期望通过非 const 迭代器所做的更改会反映在 const 迭代器中(指向不同的缓冲区,因此不会看到它们)。

【讨论】:

    【解决方案3】:

    虽然迭代器 begin() 和 end() 在此调用中是常量,但检查器怀疑调用它们会产生副作用。因此,结果可能会根据调用顺序而有所不同。我遇到了类似的问题,不得不通过使用两个局部变量来关闭检查器来解决它。

    【讨论】:

      猜你喜欢
      • 2013-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-16
      • 1970-01-01
      • 2012-09-15
      相关资源
      最近更新 更多