【问题标题】:Implementing a String class with implicit conversion to char* (C++)使用隐式转换为 char* (C++) 实现 String 类
【发布时间】:2016-08-21 21:03:29
【问题描述】:

根据我在几个地方读到的内容,这可能是不可取的(这可能是 std::string 尚未这样做的原因),但在受控环境和谨慎使用的情况下,我认为它可能可以编写一个字符串类,当第三方库方法(仅接受 char* 作为参数)需要时,该类可以隐式转换为适当的可写字符缓冲区,并且仍然表现得像具有 Find() 等方法的现代字符串、Split()、SubString() 等。虽然稍后我可以尝试实现通常的其他字符串操作方法,但我首先想问一下执行此主要任务的高效且安全的方法。目前,我们必须分配一个大约为第三方方法预期的 char* 输出的最大大小的 char 数组,将其传递到那里,然后将返回的 char* 转换为 std::string 以便能够使用它允许使用方便的方法,然后再次使用 string.c_str() 将其 (const char*) 结果传递给另一个方法。这既冗长又使代码看起来有点混乱。

到目前为止,这是我最初的实现:

MyString.h

#pragma once
#include<string>

using namespace std;

class MyString
{
private:
    bool mBufferInitialized;
    size_t mAllocSize;
    string mString;
    char *mBuffer;

public:
    MyString(size_t size);
    MyString(const char* cstr);
    MyString();
    ~MyString();
    operator char*() { return GetBuffer(); }
    operator const char*() { return GetAsConstChar(); }
    const char* GetAsConstChar() { InvalidateBuffer(); return mString.c_str(); }

private:
    char* GetBuffer();
    void InvalidateBuffer();
};

MyString.cpp

#include "MyString.h"

MyString::MyString(size_t size)
    :mAllocSize(size)
    ,mBufferInitialized(false)
    ,mBuffer(nullptr)
{
    mString.reserve(size);
}

MyString::MyString(const char * cstr)
    :MyString()
{
    mString.assign(cstr);
}

MyString::MyString()
    :MyString((size_t)1024)
{
}

MyString::~MyString()
{
    if (mBufferInitialized)
        delete[] mBuffer;
}

char * MyString::GetBuffer()
{
    if (!mBufferInitialized)
    {
        mBuffer = new char[mAllocSize]{ '\0' };
        mBufferInitialized = true;
    }

    if (mString.length() > 0)
        memcpy(mBuffer, mString.c_str(), mString.length());

    return mBuffer;
}

void MyString::InvalidateBuffer()
{
    if (mBufferInitialized && mBuffer && strlen(mBuffer) > 0)
    {
        mString.assign(mBuffer);
        mBuffer[0] = '\0';
    }
}

示例用法(main.cpp)

#include "MyString.h"
#include <iostream>

void testSetChars(char * name)
{
    if (!name)
        return;
    //This length is not known to us, but the maximum 
    //return length is known for each function. 
    char str[] = "random random name";
    strcpy_s(name, strlen(str) + 1, str);
}


int main(int, char*)
{
    MyString cs("test initializer");
    cout << cs.GetAsConstChar() << '\n';
    testSetChars(cs);
    cout << cs.GetAsConstChar() << '\n';
    getchar();
    return 0;
}

现在,我计划在执行任何其他操作之前在几乎所有方法中调用 InvalidateBuffer()。现在我的一些问题是:

  1. 在内存/性能和/或安全性方面是否有更好的方法,尤其是在 C++ 11 中(除了我计划很快添加到其中的通常的移动构造函数/赋值运算符)?
  2. 我最初使用字符的 std::vector 实现了“缓冲区”,它更容易实现并且更像 C++,但担心性能。所以 GetBuffer() 方法只会返回调整大小的向量的开始指针。您认为在这里使用向量而不是 char* 有什么主要的优点/缺点吗?
  3. 我计划稍后为其添加宽字符支持。你认为两个结构的 union : {char,string} 和 {wchar_t, wstring} 将是实现该目的的方式(一次只能是这两个结构中的一个)?
  4. 是否过于矫枉过正,而不仅仅是按照通常的方式传递 char 数组指针,转换为 std::string 并使用它来完成我们的工作。期望 char* 参数的第三方函数调用在代码中被大量使用,如果它有效,我计划用这个新字符串完全替换 char* 和 std::string。

感谢您的耐心和帮助!

【问题讨论】:

  • “根据我在几个地方读到的内容,这可能是不可取的” 我会再写一遍:这看起来是个相当糟糕的主意。
  • 与第三方库一起使用时,为什么不直接写var.c_str()?在我看来,只要从 std::string 调用 c_str() 就可以轻松安全地解决很多工作
  • 为什么不添加一个小的适配器接口来模仿第三方库中的所有函数,只接受不作为参数并返回 std::string 的 char 指针?它对所有现有代码都是透明的,没有裸字符指针的麻烦。
  • std::string 已经允许你通过 char*: stackoverflow.com/questions/38702943/…
  • 最重要的一点是完全不清楚char *p = someString;在看代码的时候做了什么。 \0 终止了吗?我可以给它写信吗?它指向多少内存?也允许使用if(someString)someString + 5;等无意义的代码,容易隐藏bug。

标签: c++ string c++11 casting


【解决方案1】:

如果我对您的理解正确,您希望它能够正常工作:

mystring foo;
c_function(foo);
// use the filled foo

c_function 喜欢 ...

void c_function(char * dest) {
  strcpy(dest, "FOOOOO");
}

相反,我建议 (ideone example):

template<std::size_t max>
struct string_filler {
  char data[max+1];
  std::string & destination;
  string_filler(std::string & d) : destination(d) {
    data[0] = '\0'; // paranoia
  }
  ~string_filler() {
    destination = data;
  }
  operator char *() {
    return data;
  }
};

并像这样使用它:

std::string foo;
c_function(string_filler<80>{foo});

通过这种方式,您可以为 C 函数提供一个“正常”缓冲区,其最大值由您指定(无论哪种方式您都应该知道......否则调用该函数将是不安全的)。在销毁临时(根据标准,必须在函数调用的表达式之后发生)时,字符串被复制(使用std::string 赋值运算符)到由std::string 管理的缓冲区中。


解决您的问题:

您认为在这里使用向量而不是 char* 有什么主要优点/缺点吗?

是的:使用向量可以将您从手动内存管理中解放出来。这是一个巨大的专业人士。

我计划稍后为其添加宽字符支持。你认为两个结构的联合: {char,string} 和 {wchar_t, wstring} 将是实现该目的的方式(一次只能是这两个结构之一)?

工会是个坏主意。你怎么知道哪个成员当前处于活动状态?你需要一个工会以外的旗帜。你真的希望每根弦都能随身携带吗?而是看看标准库在做什么:它使用模板来提供这种抽象。

是不是太过分了[..]

编写字符串类? 是的,太多了。

【讨论】:

  • 你的实现很好。我只有一个问题 - 为什么是模板?这里的目的可以通过一个普通的旧结构以及在其构造函数中使用两个参数(字符串和最大容量)来解决,对吧?
  • 模板允许缓冲区成为结构的一部分,两者都具有自动存储持续时间。因此,模板避免了原本需要的堆分配:传入大小意味着您需要动态分配缓冲区。
  • 啊。我懂了。实际上,我的实现灵感来自您的,但有点不同。它完全删除了字符分配部分(仅限 C++ 11)。你觉得它有什么问题吗?:struct string_filler { string&amp; destination; string_filler(string&amp; d, size_t capacity) : destination(d) { destination.resize(capacity); } ~string_filler() { destination.resize(strlen(destination.c_str())); } operator char *() { return &amp;destination[0]; } };
  • 抱歉格式错误(缩进丢失)。我认为这就是我在 cmets 中所能做的。上面的代码假定所有使用它的函数都将返回正确的以 null 结尾的 C 字符串,我们的第三方库就是这种情况。
  • 我觉得不错。虽然你也可以在operator char * 实现中使用c_str()(为了与析构函数中的使用保持一致,所以只是一个小风格问题)。
【解决方案2】:

你想做的已经存在。例如,使用这个普通的旧 C 函数:

/**
 * Write n characters into buffer.
 * n cann't be more than size
 * Return number of written characters
 */
ssize_t fillString(char * buffer, ssize_t size);

C++11 起:

std::string str;
// Resize string to be sure to have memory
str.resize(80);
auto newSize = fillSrting(&str[0], str.size());
str.resize(newSize);

或不先调整大小:

std::string str;
if (!str.empty()) // To avoid UB
{
    auto newSize = fillSrting(&str[0], str.size());
    str.resize(newSize);
}

但在 C++11 之前,std::string 不能保证存储在单个连续内存块中。所以你必须先通过std::vector&lt;char&gt;

std::vector<char> v;
// Resize string to be sure to have memor
v.resize(80);
ssize_t newSize = fillSrting(&v[0], v.size());

std::string str(v.begin(), v.begin() + newSize);

您可以轻松地将它与丹尼尔的命题一起使用

【讨论】:

  • 嗨 Garf365,是的,我刚刚从 Galik 了解到 C++ 11 中的这种变化(连续内存)。不过,我对您的“没有先调整大小”示例感到困惑。如果 fillString 函数尝试写入的字符多于字符串默认分配的字符,它不会失败吗?
  • fillString 的第二个参数是缓冲区的大小。所以fillString 永远不会在字符串中填充超过size 个字符(见编辑)。很多函数都有这个参数。如果您的函数没有,请确保您的字符串大小正确,因为正如您所提到的,如果fillString 尝试在字符串中写入超过分配的内存,您将得到一个 UB。此外,如果不先调整大小,您必须检查字符串是否为至少一个元素以避免 UB
猜你喜欢
  • 2020-06-16
  • 2020-04-23
  • 1970-01-01
  • 2022-07-06
  • 2015-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多