【问题标题】:C++: Correct implementation for passing a std::string to a C function that wants to change the string?C++:将 std::string 传递给想要更改字符串的 C 函数的正确实现?
【发布时间】:2011-10-18 15:39:08
【问题描述】:

我在一个用 C 语言编写的第三方库中有一个函数:char* fix_filename_slashes(char* path)。此函数需要一个可变的 C 字符串传递给它,因此它可以根据操作系统将路径中的所有斜杠更改为正确使用。我在 Facade 中使用的所有字符串都声明为 std::strings。我试图简单地使用foo.c_str(),因为其他所有期望C字符串的函数都不会改变它并且期望const char *,但是这个函数会导致错误:Error: Argument of type "const char *" is incompatible with parameter of type "char *"

是我想出的结果:

char* tempf = const_cast<char*>(filename.c_str());
filename = std::string(fix_filename_slashes(tempf));
tempf = NULL;

认为“正确”还是有其他(更正确?)方法来完成任务?

编辑

哎呀。显然,该函数返回字符串的副本。仍然有一些很好的答案。

【问题讨论】:

  • 上面写满了未定义的行为
  • @Gene Bushuyev:你能解释一下 UB,让我知道怎么回事吗?
  • 21.3.6 ... const charT* c_str() const; ....“程序不得更改存储在数组中的任何值”

标签: c++ char stdstring


【解决方案1】:

如果字符串长度不变,可以使用指向字符串第一个字符的指针。这是 C++03 标准中未定义的行为,但所有已知的实现都可以正常工作,并且在 C++11 标准下明确允许。

fix_filename_slashes(&filename[0]);

如果字符串的大小可能发生变化,您将不得不做更多的工作。

filename.resize(max_size, 0);
append_filename_suffix(&filename[0]);
filename.resize(strlen(filename.c_str()));

【讨论】:

  • 标准中的修复是什么?
  • 经过一些测试,我确定该功能不会改变长度。所以,是的,请参阅编辑。如果返回一个副本,那么期望一个非常量成员有点愚蠢。不过我对此无能为力。
【解决方案2】:

将其转换为以 null 结尾的字符序列,存储在 std::vector 中:

template <typename Character>
std::vector<Character> to_vector(std::basic_string<Character> const& s)
{
    std::vector<Character> v;
    v.reserve(s.size() + 1);
    v.insert(v.end(), s.begin(), s.end());
    v.push_back(0);
    return v;
}

使用示例:

std::string filename = get_filename();
std::vector<char> filename_cstr = to_vector(filename);
filename = std::string(fix_filename_slashes(&filename_cstr[0]));

【讨论】:

  • 能不能用vector的iterator, iterator构造函数代替insert,还是做最后的push_back
  • vector&lt;char&gt; v(filename.begin(), filename.end()); 将字符串复制到向量会更短,然后是push_back(0);
  • @Mark, @Gene:是的,但是这样可以保证只发生一次分配(reserve 执行的分配)。如果您使用范围构造函数,您最终可能会得到两个分配(一个由范围构造函数分配,一个由push_back 分配)。
  • @Mark 是的,你可以,你可能也想做v.push_back('\0');。该模板是可选的,但很高兴将其用于 char, unsigned char, wchar_t, etc. You could then pass v.data()` 到 C 函数中。
  • 太难了,写std::vector&lt;char&gt; v(s.begin(), s.end()); v.push_back(0);
【解决方案3】:

既然你会遇到所有的麻烦,你可以只遵守 C 函数的要求并将你的字符串复制到一个 char 数组,然后在函数之后从 char 数组创建一个字符串或强制对你的原始字符串进行复制分配.

    char* temp = new char[str.size() + 1]
    // Force a copy of the result into another string
    str = (const char*)fix_filename_slashes(strncpy(temp, str.c_str(), str.size() + 1));
    delete [] temp;

【讨论】:

  • delete [] temp; 并在用str.size() 调用strncpy 之后,temp 不是以空值终止的。如果fix_filename_slashes 抛出异常,也会发生内存泄漏(可能不会,但如果?);为什么还要投到const char*? (天哪,我很挑剔)
  • 修复了代码,但是 C 函数不会抛出异常,我知道应该进行更多检查,这仅用于说明目的。
  • 关于例外情况确实如此,这就是我说我很挑剔的原因 ;-)
  • 尽管如此,仍然没有理由使用手动内存管理。使用std::vector 更简洁,更不容易出错。
  • 是的,你的解决方案很优雅,但如果有任何错误,它在一个名为 fix_filename_slashes 的函数中:-)
【解决方案4】:

如果string 使用单独的缓冲区来存储c_str 字符串,则不会修改原始字符串。

更好的是在堆栈或堆上创建一个char 缓冲区,将字符复制到其中(以空结尾),调用修复函数,然后将缓冲区分配回字符串。

【讨论】:

    【解决方案5】:

    这是另一种方法,需要进行一些设置,但之后会自动运行。它依赖于一个临时对象,该对象获取原始字符串的副本并将修改后的字符串复制回析构函数中。显然,所有这些复制都不会太高效,但在大多数情况下,效率并不重要。

    class mutable_string
    {
    public:
        mutable_string(std::string & str, int maxlen = 0) : m_str(str)
        {
            m_buffer.resize(max(maxlen, str.length()) + 1);
            memcpy(&m_buffer[0], str.c_str(), str.length()+1);
        }
        ~mutable_string()
        {
            m_str = m_buffer;
        }
        operator char* ()
        {
            return &m_buffer[0];
        }
    private:
        std::string &     m_str;
        std::vector<char> m_buffer;
    };
    
    fix_filename_slashes(mutable_string(filename));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-09
      • 1970-01-01
      相关资源
      最近更新 更多