优化字符串创建

Optimizing string creation

我有以下 class 的模拟代码,它使用属性设置文件名:

#include <iostream>
#include <iomanip>
#include <sstream>

class Test {
    public:
        Test() { id_ = 1; }
        /* Code which modifies ID */

        void save() {
            std::string filename ("file_");
            filename += getID();
            std::cout << "Saving into: " << filename <<'\n';
        }

    private:
         const std::string getID() {
             std::ostringstream oss;
             oss << std::setw(4) << std::setfill('0') << id_;
             return oss.str();
         }

         int id_;
};

int main () {
    Test t;
    t.save();
}

我担心的是 getID 方法。乍一看,它似乎效率很低,因为我正在创建 ostringstream 及其对应的 string 到 return。我的问题:

1) 因为它 returns const std::string 编译器(在我的例子中是 GCC)能够优化它吗?

2) 有什么方法可以提高代码的性能吗?也许移动语义或类似的东西?

谢谢!

1) Since it returns const std::string is the compiler (GCC in my case) able to optimize it?

对 return const 对象没有意义,除非 return 通过引用

2) Is there any way to improve the performance of the code? Maybe move semantics or something like that?

Id id_ 不会改变,只是在构造函数中创建值,使用静态方法可能会有所帮助:

     static std::string format_id(int id) {
         std::ostringstream oss;
         oss << std::setw(4) << std::setfill('0') << id;
         return oss.str();
     }

然后:

Test::Test()
 : id_(1)
 , id_str_(format_id(id_))
{ }

更新:

由于 id_ 确实发生了变化,所以这个答案对问题并不完全有效,我不会删除它,因为也许有人会发现它对他的情况有用。不管怎样,我想澄清一些想法:

  • 必须是静态的才能用于变量初始化
  • 代码中有错误(现已更正),使用了成员变量id_。
  • return一个const对象按值是没有意义的,因为return按值只会将结果复制(忽略优化)到一个新变量,该变量在调用者的范围内(并且可能不是 const)。

我的建议

一个选项是随时 id_ 更改更新 id_str_ 字段(您 必须 有一个 setter 用于 id_ ),鉴于您已经在更改成员 id_ 我认为更新另一个成员不会有问题。

这种方法允许将 getID() 实现为一个简单的 getter(应该是常量,顺便说一句),没有性能问题,并且字符串字段只计算一次。

您可以这样更改 getID

std::string getID() {
  thread_local std::ostringstream oss;
  oss.str("");  // replaces the input data with the given string
  oss.clear();  // resets the error flags

  oss << std::setw(4) << std::setfill('0') << id_;
  return oss.str();
}

它不会每次都创建一个新的 ostringstream

在你的情况下这是不值得的(正如 Chris Dodd 所说 打开一个文件并写入它可能要贵 10-100 倍)...只是想知道。

还要考虑在任何合理的库实现中 std::to_string 至少与 stringstream 一样快。

1) Since it returns const std::string is the compiler (GCC in my case) able to optimize it?

这种做法是有道理的,但它基本上已经过时了(例如 Herb Sutter 建议 returning 非基本类型的 const 值)。

对于 C++11,强烈建议将 return 值作为非常量,以便您可以充分利用 rvalue 引用。

关于这个话题你可以看看:

  • Purpose of returning by const value?
  • Should I return const objects?

一种可能是这样做:

std::string getID(int id) { 
    std::string ret(4, '0') = std::to_string(id);

    return ret.substring(ret.length()-4);
}

如果您正在使用包含短字符串优化的实现(例如,VC++),这很有可能会显着提高速度(使用 [=27= 进行快速测试) ]++ 显示它的速度大约是原来的 4-5 倍)。

OTOH,如果您使用的实现 包含短字符串优化,那么它的运行速度可能会大大 变慢 .例如,运行 使用 g++ 进行相同的测试,生成的代码速度大约慢 4-5 倍。

还有一点:如果您的身份证号码可能超过 4 位,这不会给出相同的行为——它总是 returns 一个字符串恰好 4 个字符,而不是 stringstream 代码创建的最少 4 个字符。如果您的身份证号码可能超过 9999,那么此代码根本不适合您。

创建一个 ostringstream,只需要在打开文件等昂贵的操作之前创建一次,对程序的效率根本没有影响,所以不用担心。

但是,您应该担心代码中出现的一个坏习惯。值得称赞的是,您似乎已经确定了它:

1) Since it returns const std::string is the compiler (GCC in my case) able to optimize it?

2) Is there any way to improve the performance of the code? Maybe move semantics or something like that?

是。考虑:

class Test {
    // ...
    const std::string getID();
};

int main() {
    std::string x;
    Test t;
    x = t.getID();  // HERE
}

在标记为// HERE的行中,调用了哪个赋值运算符?我们想调用移动赋值运算符,但该运算符的原型为

string& operator=(string&&);

而我们实际传递给 我们的 operator= 的参数是 "reference to an rvalue of type const string" 类型——即 const string&&。 const 正确性规则阻止我们将 const string&& 静默转换为 string&&,因此当编译器创建赋值运算符函数集时,可以在此处使用(重载 set),它必须排除采用 string&&.

的移动赋值运算符

因此,x = t.getID(); 最终调用了 copy-赋值运算符(因为 const string&& 可以安全地转换为 const string&),并且你制作了一个额外的副本,如果你没有养成 const 的坏习惯 - 限定你的 return 类型。


另外,当然,getID()成员函数应该声明为const,因为它不需要修改*this对象。

所以正确的原型是:

class Test {
    // ...
    std::string getID() const;
};

经验法则是:始终按值 return,从不按 const 值 return。