【问题标题】:Converting a char* returned by C API to C++ string将 C API 返回的 char* 转换为 C++ 字符串
【发布时间】:2017-09-27 16:48:03
【问题描述】:

我在围绕我正在使用的 C API 的 C++ 标头包装器中找到了这段代码:

static string GetString(const char* chString)
{
    string strValue;
    if (NULL != chString)
    {
        strValue.swap(string (chString));
        releaseMemory((void*&)chString);
        chString = NULL;
    }
    return strValue;
}

我想作者试图给字符串strValuechString 的所有权,然后释放空缓冲区。我怀疑这是非常错误的(包括它是 const char*),但它实际上似乎适用于 MSVC 12。至少我还没有看到它崩溃。

假设 C API 和 C++ 库使用相同的堆(以便字符串可以在必要时重新分配缓冲区并最终释放它),有没有办法正确实现这一点?这个怎么样?

template <typename T> struct Deleter { void operator()(T o) { releaseMemory((void*&)o); } };

static std::string GetString(char* chString)
{
    if (NULL == chString)
        return std::string();
    return std::string(std::unique_ptr<char[], Deleter<char[]>>(chString).get());
}

再次假设 C API 使用与 std::string 相同的堆。

如果这也是非常错误的,那么是否有一个不可变的、拥有 C 风格的字符串包装器?类似于string_view 但不可变(所以const char* 输入可以)和拥有(所以它删除了C 字符串,可能在其dtor 中使用自定义删除器)?

【问题讨论】:

  • C API 应该记录那个字符串是什么,如果它以某种方式被malloc-ed 负责free-ing 它。
  • std::string 不拥有chString 指向的缓冲区的所有权。它会复制它。
  • strValue.swap(string (chString));strValue = chString; 的不必要的复杂化。
  • unique_ptr 方法是错误的,因为它在应该使用releaseMemory 时使用了delete []。也没有所有权。 GetString 创建了const char * 的副本,然后以一种奇怪的方式破坏了原始文件。 chString = NULL; 也完全没有意义。
  • @MattChambers 是的。就像 molbdnilo 所说,如果你直接将chString 分配给strValue,你会得到基本相同的结果。

标签: c++ optimization memory-management


【解决方案1】:

我想作者试图给字符串strValuechString 的所有权,然后释放空缓冲区。

没有。它对chString指向的字符数据进行(低效且容易出错的)拷贝,然后释放chString指向的内存(如果拷贝抛出异常将跳过),然后返回拷贝.

假设 C API 和 C++ 库使用同一个堆

这不是一个正确的假设,甚至不是必要的假设。副本可以使用它想要的任何堆。

有没有办法正确实现这一点?这个怎么样?

使用std::unique_ptr 和自定义deleter 是正确的,但没有理由使用std::unique_ptrT[] 数组特化。

代码可以简化成这样:

void Deleter(char* o) { releaseMemory((void*&)o); }

static std::string GetString(char* chString)
{
    std::string strValue;
    if (chString) {
        std::unique_ptr<char, decltype(&Deleter)>(chString, &Deleter);
        strValue = chString;
    }
    return strValue;
}

或者,只是去掉检查chString 是否为空,实际上不需要它。 std::string 可以从一个空的char* 构造,而std::unique_ptr 不会用空指针调用它的deleter

void Deleter(char* o) { releaseMemory((void*&)o); }

static std::string GetString(char* chString)
{
    std::unique_ptr<char, decltype(&Deleter)>(chString, &Deleter);
    return std::string(chString);
}

【讨论】:

    【解决方案2】:

    对于我的最后一个问题,这似乎是一个很好的解决方案(以及能够像字符串一样使用 char* 而无需复制它的最终目标)?

    template <typename DeleterT = std::default_delete<const char*>>
    class c_str_view
    {
        public:
        unique_ptr<const char*, DeleterT> strPtr_;
        size_t len_;
    
        c_str_view() {}
        c_str_view(const char* charPtr) : strPtr_(charPtr), len_(strlen(charPtr)) {}
        c_str_view(const char* charPtr, size_t len) : strPtr_(charPtr), len_(len) {}
    
        operator std::string_view () const
        {
            return string_view(strPtr_.get(), len_);
        }
    };
    

    如果是这样,由于 string_view 即将到来,是否有充分的理由不在即将发布的标准中?当然,它只对 string_view 有意义,因为任何到 std::string 的转换都会导致复制并使整个练习变得毫无意义。

    这是一个测试: http://coliru.stacked-crooked.com/a/9046eb22b10a1d87

    【讨论】:

    • "这对我的最后一个问题来说似乎是一个很好的解决方案" - 不。首先,这应该添加到您的问题中,而不是作为答案发布。其次,它没有解决使用releaseMemory() 正确释放原始char* 内存的问题。事实上,这段代码将获得char* 的所有权,并尝试使用delete[] 来发布它。您不能将 delete 与来自基于 C 的 API 的内存一起使用。
    • 拥有所有权是目的。 C API 赋予调用者所有权;像这样的课程会接受并返回它,而不是在返回之前复制并释放原件。我特别做了模板化,所以我可以使用releaseMemory() 自定义删除器。 (这是试图回答我的最后一个问题,但如果它们更全面,我肯定会更喜欢我自己的其他答案)
    • 在您展示的模板中,您接受自定义 deleter type 作为模板参数,但您没有使用实际的 deleter 构造 std::unique_ptr 对象。如果您阅读了std:::unique_ptr 文档,您应该将实际的删除器作为构造函数参数传递,而不仅仅是模板参数(它定义了构造函数参数的类型)
    • 你确定吗? GCC 似乎在没有明确传递的情况下使用删除器(我不经常使用自定义删除器!);将使用测试代码编辑答案
    • read the std::unique_ptr documentation。如果您没有在构造函数中传递删除器,则使用模板中指定的类型对默认值进行初始化。当删除器是具有覆盖 operator() 的结构/类时,这很好,但当删除器是普通函数或 lambda 时,就不行了。所以最好总是将自定义删除器作为构造函数参数传递,这样你就知道你得到了什么。
    猜你喜欢
    • 2018-09-02
    • 2016-02-05
    • 2012-01-16
    • 2018-03-27
    • 2013-05-13
    • 1970-01-01
    • 2010-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多