【问题标题】:Homemade toupper: looks the same but not identical自制toupper:看起来相同但不相同
【发布时间】:2020-01-22 13:15:36
【问题描述】:

为了我的优化,我想在 Rcpp 中得到一个像样的toupper。我对 C++ 很陌生,据我所知,我已经做到了:

#include <Rcpp.h>
using namespace Rcpp;

void C_toupper_String(const String& s) {
  for (char *p =(char *)s.get_cstring();*p!=0;p++) *p = toupper(*p);
}
// [[Rcpp::export]]
StringVector C_toupper(StringVector const& vecteur) {
  StringVector res=clone(vecteur);
  for (int i(0); i < res.size(); ++i) {
    C_toupper_String(res[i]);
  }
  return res;
}

/*** R
teststring <- "HeY I arNaud"
C_toupper(teststring)
toupper(teststring)
identical(C_toupper(teststring),toupper(teststring))
*/

但是,它不能正常工作。

> C_toupper(teststring)
[1] "HEY I ARNAUD"

> toupper(teststring)
[1] "HEY I ARNAUD"

> identical(C_toupper(teststring),toupper(teststring))
[1] FALSE

有什么问题?如果可能,我不想将String 转换为std::string,因为我想了解发生了什么:进入C++ 的目的是能够避免复制和转换。

谢谢,

阿诺

【问题讨论】:

  • 我不确定你想要达到什么目的,因为Rcpp::clone() 会复制你的数据。
  • Ralf Stubner :我希望能够在我想要的时候做,而不是在我不想做的时候。我想了解 Rcpp::String 的工作原理,并直接能够安全地修改它。无论如何,它甚至不会复制数据,因为我注意到测试字符串已被修改......这完全失败了哈哈
  • teuder.github.io/rcpp4everyone_en/170_string.html get_cstring 正在制作副本,因此您修改的是副本,而不是原件
  • 是什么让你认为在 Rcpp 中重新实现会比 R 的内置 toupper 函数更快?
  • @OlivierSohn 这不正确,链接也没有声明。

标签: c++ r string rcpp toupper


【解决方案1】:

为什么这两个字符串不测试identical 的问题很难解释——这两个字符串在检查它们的原始字节时看起来肯定是相同的(通过charToRaw),它们不携带属性,它们不有一个编码集。所以,真的,它们应该是相同的。

要解开这个谜团,我们需要了解您的 C++ 代码实际上在做什么。更具体地说,C_toupper_String 中的 C 风格演员正在做什么。因为他们的危险,you should never use C-style casts。您的代码遇到问题纯粹是因为该演员表。

为什么?因为String::get_cstring 返回char const*。您将其投射到char*,从而抛弃了它的constness。这可以是安全的,但前提是底层存储不是const。否则为undefined behaviour (UB)。由于代码重写(例如优化),UB 的影响很难预测。在这种情况下,它似乎生成了混淆 R 字符串内部结构的代码。


你基本上不能修改Rcpp::String对象,他们不允许。但是,如果您只是想避免复制,那么您的代码无论如何都达不到目标,因为您的 C_toupper 函数在第一步中明确复制了输入。

正如 Dirk 所说,解决这个问题的正确方法是使用可用的 Rcpp API。在字符串修改的情况下,这意味着将您的输入转换为std::string,执行修改,然后再转换回来。这确实复制了,但您当前的代码也是如此。这是编写此代码的一种好方法:

#include <Rcpp.h>
#include <cctype>
#include <string>

// [[Rcpp::export]]
Rcpp::StringVector C_toupper(Rcpp::StringVector const& vec) {
    std::vector<std::string> res(vec.begin(), vec.end());
    for (std::string& str : res) {
        for (char& c : str) c = std::toupper(c);
    }

    return Rcpp::wrap(res);
}

请注意,这有时会产生错误的结果,因为std::toupper 根本无法处理某些 Unicode 特征。 R 的toupper 做得更好,但也有一些问题。 适当的解决方案是使用{stringr} 或 {stringi} 包。

【讨论】:

  • 不错的答案,感谢您抽出宝贵时间。
  • 感谢您所做的一切。我知道直接尝试处理内存是不安全的。我会检查我的代码,看看我没有在其他地方破坏 const。我猜存储比单个字符链更复杂。
  • @ArnaudFeld 或多或少是一个char数组+header,用来存储字符串数据。并不是结构太复杂,而是 R 使用全局字符串缓存,并且直接在缓存中修改某些内容打破了 R 所做的假设。
【解决方案2】:

为什么不使用 C++ 库的单线器?除非您真正在 C++ 方面有经验,否则您可能不太可能击败它。以下代码只是缩进显示在这里,这是我的 R 会话中的一行。

R> Rcpp::cppFunction("std::string tU(std::string s) { std::string u(s); \
 for (unsigned int i=0; i<u.length(); i++) u[i] = std::toupper(u[i]); return(u); }")
R> tU("aBcDe")
[1] "ABCDE"
R> 

【讨论】:

  • 这并不能解释为什么 OP 的代码会产生一个看起来相同的字符串,但不会将 identical 测试为 toupper(或您的代码)的结果。即使用charToRaw检查它们也没有任何区别,它们的编码是相同的,而且它们似乎没有任何属性。
  • 感谢您的回答。但是,正如我所说,我宁愿避免转动 std::string。正如@KonradRudolph 所说,重点是不了解会发生什么。
  • @Konrad: 当然可以,但是如前所述,如果一个人的目标是在脚上开枪,直接进入 C 级例程是一个不错的选择;-) 如果 OP 不是我的问题编写一些未通过identical 的代码。我们有(字面上)数千个通过的 Rcpp 代码的入口点。他的问题,不是我的。对他来说无疑是一个很好的学习机会。
  • 或者换种说法:我们的目标是提供更高级别的访问权限,这样用户就不必担心按位相同。人们通常只是免费获得它:Rcpp 创建的对象通常与 R 创建的对象没有区别。在这里,他设法用绕过我们结构的本土代码打破了这一点。这将是......他的问题不是我的,但正如所说,可能是一个很好的学习和调试机会。
  • @DirkEddelbuettel 在教授正确的方法而不是直接回答 XY 问题时,我完全支持您。但是,您在这里忽略了一个非常有趣的问题:identical 失败的原因绝对不明显——事实上,即使查看了R_compute_identical 的源代码,我也不知道会发生什么这里。我唯一能看到的是他们的sxpinfo-&gt;obj 标志不同。此外,在 C++ 端避免复制听起来不像是一个愚蠢的想法。有比 OP 更好的方法来做到这一点。
猜你喜欢
  • 1970-01-01
  • 2016-05-22
  • 2015-08-31
  • 1970-01-01
  • 1970-01-01
  • 2013-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多