• 使用QStringBuilder进行字符串连接


    使用QStringBuilder进行字符串连接

    来源 https://www.qt.io/cn/blog/2011/08/22/string-concatenation-with-qstringbuilder

    Published on Monday August 22, 2011 by Debao Zhang in C++PerformanceQtqt-labs-chinese | Comments

    原文链接:Olivier Goffart - String concatenation with QStringBuilder

    QString和QByteArray提供了非常便利的operator+,以允许你写这样的代码:

    QString directory = /*...*/, name = /*...*/;
    QString dataFile = directory + QLatin1Char('/') + name + QLatin1String(".dat");

    非常方便。

    QLatin1CharQLatin1String用在这儿只是出于正确性的考虑,你在编写自己的应用程序时可以省略它们。

    虽然我们有了很方便的东西,但是这种表达式的性能如何呢?
    每个operator+都会创建一个一个临时字符串,然后将其丢弃,这意味着,会有很多次的内存分配和拷贝。

    如果(像下面)这样做的话,将会快很多。

    QString dataFile = directory;
    dataFile.reserve(directory.size() + 1 + name.size() + 4);
    dataFile += QLatin1Char('/');
    dataFile += name;
    datafile += QLatin1String(".dat");

    只需要一次内存分配和拷贝,这是最优的结果。但不幸的是,看起来不是那么好。
    倘若第一个表达式和上面这个一样快会怎么样?好消息是——这是可能实现的。

    在Qt 4.6中我们引入一个隐藏的类:QStringBuilder。
    在4.8中我们添加了对QByteArray的支持。

    由于这是源码不兼容的(见下文),你需要显式地启用它。
    在Qt 4.7中启用它的方法在4.7 QString文档中有介绍。
    但是这种方法现在被废弃了,而且在Qt 4.8中,这个宏已被新的QT_USE_QSTRINGBUILDER宏所替代。要想受益于QByteArray的改变,你必须使用新的宏。

    为了使其工作,我们使用了一个被称为表达式模板(Expression template)的技术。
    我们修改了一些接受字符串的operator+,使其返回一个特殊的模板类,它的结果将会延迟(lazily)计算。

    举例来说,当定义QT_USE_QSTRINGBUILDER后,string1 + string2的类型将是可以隐式转换成QString的QStringBuilder<QString, QString> 。

    这是源码不兼容的,因为你可能写有假定operator+的返回值是QSting类型的代码。

    QVariant v = someString + someOtherString;
    QString s = (someString + someOtherString).toUpper();

    解决方案是显式转换成QString:

    QVariant v = QString(someString + someOtherString);
    QString s = QString(someString + someOtherString).toUpper();

    编译Qt自身和Qt Creator时,QT_USE_QSTRINGBUILDER 已经被启用了。
    一些修复源码兼容性问题的提交(commit)有:
    5d3eb7a1对于尚未支持QByteArray的早期版本,和7101a3fa在Qt 4.8中添加对QByteArray支持。

    技术细节

    考虑到本实现展示了许多很好的模板特性,我认为在本文中解释一点这个类的细节将会非常有趣。它是高度技术性的,但使用它的话却完全不要求你理解这些。

    一切均在qtringbuilder.h中,为了便于理解本文中的贴出的代码片段可能稍微做了一点简化。

    让我们从operator+的实现开始看起:

    template <class A, class B>
    QStringBuilder<typename QConcatenable<A>::type, typename QConcatenable<B>::type>
    operator+(const A &a, const B &b)
    {
    return QStringBuilder<typename QConcatenable<A>::type,
    typename QConcatenable<B>::type>(a, b);
    }

    该操作符使用SFINAE来做到仅对支持字符串连接的类型起作用。实际上,QContatenable是一个只对QString、QLatin1String、QChar、QStringRef、QCharRef以及QByteArray和char*进行了特化的内部模板类。
    QConcatenable<T>::type是类型T的别名(typedef),且只对这些特殊的类型有效。
    比如,由于QConcatenable<QVariant>::type不存在,operator+用于QVariant时将不会被启用。

    operator+(a,b) 简单地返回QStringBuilder<A, B>(a, b);
    像这样的一些东西string1 + string2 + string3,其结果的类型将是 QStringBuilder< QStringBuilder <QString, QString> , QString>

    现在我们可以看一下QStringBuilder类

    template <typename A, typename B>
    class QStringBuilder
    {
    public:
    const A &a;
    const B &b;

    QStringBuilder(const A &a_, const B &b_) : a(a_), b(b_) {}

    template <typename T> T convertTo() const;

    typedef typename QConcatenable<QStringBuilder<A, B> >
    ::ConvertTo ConvertTo;
    operator ConvertTo() const { return convertTo<ConvertTo>(); }
    };

    依赖于类型A和B,别名ConvertTo将代表QByteArray或QString,稍后我们会看到这是如何做到的。因此QStringBuilder只保存它的操作数的引用。

    当QStringBuilder隐式地被转换成QString或QByteArray时,函数convertTo()将被调用:

    template <typename A, typename B> template<typename T>
    inline T QStringBuilder<A, B>::convertTo()
    {
    const uint len = QConcatenable< QStringBuilder<A, B> >::size(*this);
    T s(len, Qt::Uninitialized);
    typename T::iterator d = s.data();
    QConcatenable< QStringBuilder<A, B> >::appendTo(*this, d);
    return s;
    }

    该函数创建一个合适大小的未初始化的QString或QByteArray并把这些字符复制到里面。
    实际的拷贝委托给了QConcatenable<QStringBuilder<A, B> >::appendTo
    将独立的片段进行合并的是用QStringBuilder<A, B>偏特化后的模板QConcatenable。如果同一行中有许多operator+,那么A将是另一个QStringBuilder类型。

    template <class A, class B>
    struct QConcatenable< QStringBuilder<A, B> >
    {
    typedef QStringBuilder<A, B> type;
    typedef typename QtStringBuilder::ConvertToTypeHelper<
    typename QConcatenable<A>::ConvertTo,
    typename QConcatenable<B>::ConvertTo>::ConvertTo ConvertTo;
    static int size(const type &p)
    {
    return QConcatenable<A>::size(p.a)
    + QConcatenable<B>::size(p.b);
    }
    template<typename T> static inline void appendTo(
    const type &p, T *&out)
    {
    QConcatenable<A>::appendTo(p.a, out);
    QConcatenable<B>::appendTo(p.b, out);
    }
    };

    函数QConcatenable::appendTo负责将字符串拷贝到最终的缓冲区。

    举例来说,对于QString,这是QConcatenable看起来的样子

    template <> struct QConcatenable<QString>
    {
    typedef QString type;
    typedef QString ConvertTo;
    static int size(const QString &a) { return a.size(); }
    static inline void appendTo(const QString &a, QChar *&out)
    {
    const int n = a.size();
    memcpy(out, reinterpret_cast<const char*>(a.constData()),
    sizeof(QChar) * n);
    out += n;
    }
    };

    我们如何才能知道我们需要转换成QString还是QByteArray?让我们来尝试理解一下ConvertTo类型是如何确定的:

    namespace QtStringBuilder {
    template <typename C, typename D> struct ConvertToTypeHelper
    { typedef C ConvertTo; };
    template <typename T> struct ConvertToTypeHelper<T, QString>
    { typedef QString ConvertTo; };
    }

    ConvertToTypeHelper被用来计算QConcatenable< QStringBuilder<A, B> >::ConvertTo。它是一个模板计算(template computation)。它可以被看作是接收两个类型参数(C和D)并以别名ConvertToTypeHelper::ConvertTo的类型返回的函数。
    默认情况下,ConvertTo总是第一个类型。但如果第二个类型是QString,模板偏特化将被使用,而QString将被“返回”。
    在实际中,这意味着只要任何一个类型是QString,QString就将被返回。

    为可感知unicode的类型(QString、QLatin1String、QChar、...)特化的QConcatenable将QString取为ConvertTo,而其他基于8位字符的类型将ConvertTo
    作为QByteArray的别名。

    现在让我们看一下关于QByteArray的特化:

    template <> struct QConcatenable<QByteArray> : private QAbstractConcatenable
    {
    typedef QByteArray type;
    typedef QByteArray ConvertTo;
    static int size(const QByteArray &ba) { return ba.size(); }
    #ifndef QT_NO_CAST_FROM_ASCII
    static inline void appendTo(const QByteArray &ba, QChar *&out)
    {
    QAbstractConcatenable::convertFromAscii(ba.constData(),
    ba.size(), out);
    }
    #endif
    static inline void appendTo(const QByteArray &ba, char *&out)
    {
    const char *a = ba.constData();
    const char * const end = ba.end();
    while (a != end)
    *out++ = *a++;
    }
    };

    与QString相同,但是Qt允许你隐式地将QByteArray转换为QString,这也是为什么这里有一个从ASCII到unicode转换的重载。通过定义QT_NO_CAST_FROM_ASCII可以禁用它。由于你不知道应用程序的开发者在他的代码中会使用何种编码,在库代码中只使用显式转换(通过QLatin1String)是一个好的实践。

    结论

    我跳过了一些细节,比如对一些像UTF-8的编码可能有不同的大小(查阅代码中的ExactSize)这些事实的支持。

    我希望你喜欢本描述。
    如果你想看到对Qt其他部分的解释,来让我们通过评论知道它。

    (顺便一提,如果你听过说QLatin1Literal,不要怕使用它。对字符串常量,编译器内置的strlen将在编译时被计算)

    ================= End

  • 相关阅读:
    python 环境搭建
    马后炮之12306抢票工具(四)--抢票Demo,2014年1月9日终结版
    Office2013插件开发Outlook篇(2)-- Ribbon
    Office2013插件开发Outlook篇(1)-- 第一个office2013插件
    ExtJS4随笔(1) -- 在VS中加入Ext4的智能提示
    PHP中使用CURL请求页面,使用fiddler进行抓包
    微博公众平台(二)-- Token验证代码
    微博公众平台(一)-- 开发者接口配置信息
    cookie介绍
    jquery--工具插件
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/12162553.html
Copyright © 2020-2023  润新知