IString / IStringView / IStringViewStash
人手一个 String 库
在 ICore 中,有 std::string, QString, QByteArray, const char* 等等一系列的内容可以表示一个字符串类型,那为什么还要增加复杂性,从新搞一个 IString 呢?
QString 是一个非常优秀的字符串库,它是以 utf-8 格式存储字符串,所以用户如果想要获取字节流,必须通过他的函数 toUtf8() 转换为 QByteArray。 QByteArray 表示的是字节数据的粗出内容。 std::string 里面存储的是 字节流,并且编码方式不确定。 const char* 一般是放在静态或全局区的字符串内容,也有可能是放在堆里。 所以为了统一这种情况,我们将这些内容给封装成一个, IString, 来解决他们相互冲突,相互转换的问题。
IString
声明
IString 的声明方式如下:
| struct IString
{
private:
enum class Type {
IStringView, // default, and empty
QByteArray,
StdString,
};
public:
IString() = default;
~IString();
IString(const IString& other);
IString(IString&& other) noexcept;
IString& operator=(const IString& other);
IString& operator=(IString&& other) noexcept;
IString(const IString*);
IString(const std::string*);
IString(const QByteArray*);
IString(const char*);
IString(const QByteArray& byteArray);
IString(QByteArray&& byteArray) noexcept;
IString(const std::string& stdString);
IString(std::string&& stdString) noexcept;
IString(const QString&) noexcept;
IString(IStringView&& stringView);
IString(const IStringView& stringView);
IString& operator=(const IString*);
IString& operator=(const QByteArray*);
IString& operator=(const std::string*);
IString& operator=(const char*);
IString& operator=(const QString&);
IString& operator=(const QByteArray& byteArray);
IString& operator=(QByteArray&& byteArray) noexcept;
IString& operator=(const std::string& stdString);
IString& operator=(std::string&& stdString) noexcept;
IString& operator=(IStringView&& stringView);
IString& operator=(const IStringView& view);
IString& operator=(std::nullptr_t);
bool operator ==(const IString&) const;
bool operator !=(const IString&) const;
bool operator <(const IString&) const;
public:
std::size_t length() const;
std::size_t size() const;
bool isSolid() const;
bool isEmpty() const;
IString& solidify();
operator bool() const;
operator IStringView() const;
public:
template<typename T>
IResult<T> value() const;
public:
IStringViewList split(char) const;
IStringViewList split(const IString&) const;
bool startWith(const IString&) const;
bool equal(const IString&) const;
bool equalIgnoreCase(const IString&) const;
bool containIgnoreCase(const IString&) const;
public:
QString toQString() const;
std::string toStdString() const;
QByteArray toQByteArray() const;
private:
void clear();
void copyFrom(const IString& other);
void moveFrom(IString&& other) noexcept;
private:
Type m_type{Type::IStringView};
void* m_data{};
public:
IStringView m_view{};
};
using IStringList = QList<IString>;
Q_DECLARE_METATYPE(IString)
|
在上面的代码中,看到最多的是他的构造函数 和赋值函数,用户可以传递任意的字符串类型来构造一个IString。
构造
用户可以传递如下的类型来构造 IString,
- const char*, const std::string& ,const QString&, const QByteArray&, const IString&, const IStringView&,
这些类型在构造IString 的时候, 其内容会被复制到 IString 中, IString 会单独存储一份副本。
- std::string&&, QByteArray&&。 IStringView&&, IString&&
这些类型会通过移动构造函数,将参数移动到 IString 里面。
- const IString*, const std::string*, const QByteArarry*
这些类型就很有意思了。比如用户有一个 std::string 对象,用户知道这个对象在 构造为 IString 直到IString 生命周期结束, std::string 对象都会存在, 则此时用户可以将 std::string 取地址传递为 std::string*/ const std::string* 类型给 IString,这是IString会通过获取std::string 的引用, std::string_view, 来存储该对象。这样对象就不需要额外的创建和拷贝。
其他的赋值构造函数也是如此。
提供的函数接口
对象本身的内容的查询
| // 获取字节数的长度
std::size_t length() const;
std::size_t size() const;
// 判断该对象是否是实体对象,也就是说该对象是否内部有字符串的副本。
bool isSolid() const;
// 判断字符中内容是否为空
bool isEmpty() const;
// 对没有字符串副本的对象,创建一个字符串副本。
IString& solidify();
// 判断该对象是否为空
operator bool() const;
// 获取该对象的 view
operator IStringView() const;
|
转换对象
| template<typename T>
IResult<T> value() const;
|
这个内容很有意思,它可以将该对象转换为一个 整型数值,浮点型数值 ; 字符串类型值 , std::string, QString, QByteArray, IStringView, QDate, QTime, QDateTime, 类型的值。 如果转换失败, 返回 std::nullopt 对象, 此时返回值就会判断失败。
字符串的操作
| // 拆分字符串
IStringViewList split(char) const;
IStringViewList split(const IString&) const;
// 判断字符串是否是以某字符串开头
bool startWith(const IString&) const;
// 判断是否相等
bool equal(const IString&) const;
// 判断是否相等,忽略字符大小写
bool equalIgnoreCase(const IString&) const;
// 判断是否包含,忽略大小写
bool containIgnoreCase(const IString&) const;
|
字符串的转换
| // 将 IString 转换为 QString 类型
QString toQString() const;
// 将 IString 转换成 std::string 类型
std::string toStdString() const;
// 将 IString 转换为 QByteArray 类型。
QByteArray toQByteArray() const;
|
IStringView
IStringView 是重载 std::string_view, 它包含两个内容,一个是字符串的起始地址,另外一个是字符串的长度,之所以要再一次封装,是因为ICore想要提供更多的功能。
声明
IStringView 声明如下:
| class IStringViewList;
class IStringView : public std::string_view
{
public:
IStringView() = default;
explicit IStringView(const std::string& data);
explicit IStringView(const QByteArray& data);
explicit constexpr IStringView(std::string_view data);
explicit constexpr IStringView(const char* data, std::size_t length);
constexpr IStringView(const char* data);
constexpr IStringView(const IStringView& data);
operator QByteArray() const;
bool operator ==(const char*) const;
bool operator ==(IStringView) const;
bool operator !=(IStringView) const;
bool operator <(IStringView) const;
public:
static uint qHash(const IStringView *obj, uint seed = 0);
public:
QString toQString() const;
std::string toStdString() const;
QByteArray toQByteArray() const;
asio::const_buffer toAsioBuffer() const;
public:
IStringView substr(const size_type _Off, size_type _Count=std::string::npos) const;
IStringView trimmed();
IStringViewList split(char) const;
IStringViewList split(IStringView) const;
bool startWith(IStringView prefix) const;
bool endWith(IStringView suffix) const;
bool equalIgnoreCase(IStringView) const;
bool containIgnoreCase(IStringView) const;
};
|
构造
在上面的内容可以看见 IStringView 可以通过 const std::string&, const QByteArray&, const char*, std:;string_view 等类型进行构造。
注意这里有一个点就是不能使用 QString 进行构造, 这个是因为 QString 内部表示的是 Utf8 字符,而不是单个字符。
还有一点需要注意,这里对 IStringView 的声明周期不能超过构造它的字符串的生命周期,否则将会遇到 内存异常的问题。
提供的函数
转换函数
| // 转换为 QString类型
QString toQString() const;
// 转换为 std::string 类型
std::string toStdString() const;
// 转换为 QByteArray 类型
QByteArray toQByteArray() const;
// 转换为 asio::const_buffer 类型
asio::const_buffer toAsioBuffer() const;
|
最后一个 asio::const_buffer 是用于发送数据而转换使用的。
操作函数
| // 提取子串
IStringView substr(const size_type _Off, size_type _Count=std::string::npos) const;
// 出去空白字符
IStringView trimmed();
// 拆分字符串
IStringViewList split(char) const;
IStringViewList split(IStringView) const;
// 判断字符串是否以另外一个字符串起始
bool startWith(IStringView prefix) const;
// 判断字符串是否以另外一个字符串结束
bool endWith(IStringView suffix) const;
// 判断字符串是否相等,忽略大小写
bool equalIgnoreCase(IStringView) const;
// 判断字符串是否包含,忽略大小写
bool containIgnoreCase(IStringView) const;
|
如果用户需要判断字符串是否完全相等,使用 == 即可。
IStringViewList
在上面的拆分功能中,有IStringViewList 返回类型,他的定义如下:
| class IStringViewList : public QList<IStringView>
{
public:
IStringViewList() = default;
IStringViewList(QList<IStringView> data);
public:
std::string join(IStringView);
};
|
这个类型是对 QList 的一个封装,提供了一个 join 函数。它表示的是一连串的字符串视图。
IStringViewStash
可记得在上面说 IStringView 的声明周期不能长于他的构造对象的声明周期?
为了解决这个问题,我们创建了IStringViewStash这个对象。这个对象的本质是将一个 string 对象传递进去,缓存起来,并返回 一个IStringView 对象。这样只要 IStringViewStash 对象的声明周期没有结束,IStringView 就可以被安全的使用。
声明
| class IStringViewStash
{
public:
IStringViewStash() = default;
virtual ~IStringViewStash() = default;
public:
IStringView stash(const char* data);
IStringView stash(QByteArray&& data);
IStringView stash(const QByteArray& data);
IStringView stash(const QString& data);
IStringView stash(std::string&& data);
IStringView stash(const std::string& data);
IStringView stash(IString&& data);
IStringView stash(const IString& data);
IStringView stash(IStringView data);
private:
std::list<IString> m_stashed;
};
|
构造
在上面的代码中,我们可以看见,IStringViewStash 只有一种类型的函数签名,就是 stash 函数。它可以传递任何的 字符串类型,然后返回给用户一个 IStringView 类型的数据。这样用户的IStringView 类型的数据就不会担心过期。
使用 IStringViewStash
在IWebCore 中,我们使用 IStringViewStash 的一个例子就是在IHttpCore库中使用的
| class IHttpRequestImpl : public IStringViewStash
{
// ...
}
class IHttpResponseRaw : public IStringViewStash
{
// ...
}
|
在上面的例子中,IHttpRequestImpl 是以一个Http 请求为其生命周期的。在这个请求进行的过程中,需要缓存一些字符串。我们直接使 IHttpRequestImpl 继承于 IStringViewStash, 这样既保证了能够缓存字符串,也能够保证字符串的合理的生命周期。
IHttpResponseRaw也是一样的,用户在一个http请求中可以定义多个response, 但后面定义的会覆盖前面定义的,它通过缓存字符串,解决了字符串多次拷贝等一系列的问题。