优化字符串创建

时间:2015-01-30 15:25:12

标签: c++ c++11 c++14

我有一个类的模拟代码,它使用一个属性来设置文件名:

#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以返回,因此效率似乎非常低效。我的问题:

1)因为它返回const std::string是编译器(在我的情况下是GCC)能够优化它吗?

2)有没有办法改善代码的性能?也许移动语义或类似的东西?

谢谢!

4 个答案:

答案 0 :(得分:1)

  

1)因为它返回const std :: string是编译器(在我的情况下是GCC)   能够优化吗?

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

  

2)有没有办法改善代码的性能?也许搬家吧   语义或类似的东西?

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 _。
  • 按值返回 const 对象是没有意义的,因为按值返回只会将结果复制(忽略优化)到新变量,该变量位于调用者的范围(可能不是const)。

我的建议

一个选项是随时更新id_str_字段id_更改(必须设置id_的设置者),因为您已经已经改变成员id_我认为没有问题更新另一个。

这种方法允许将getID()实现为一个简单的getter(应该是const,btw),没有性能问题,字符串字段只计算一次。

答案 1 :(得分:1)

在打开文件之类的昂贵操作之前创建ostringstream一次,对您的程序的效率并不重要,所以不要担心它

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

  

1)因为它返回const std::string是编译器(在我的情况下是GCC)能够优化它吗?

     

2)有没有办法改善代码的性能?也许移动语义或类似的东西?

是。考虑:

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

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

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

string& operator=(string&&);

我们实际传递给我们的 operator=的论点属于&#34;对const string&#34;类型的右值的引用 - 即const string&&。 const-correctness规则阻止我们将const string&&静默地转换为string&&,因此当编译器创建一组赋值运算符函数时,它可以在这里使用(< em>重载集),它必须排除采用string&&的移动赋值运算符。

因此,x = t.getID();最终会调用 副本 - 分配运算符(因为const string&&可以安全地转换为const string&)如果只有你没有陷入const的坏习惯 - 你的退货类型合格,你就可以制作一份本可以避免的额外副本。


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

所以正确的原型是:

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

经验法则是: 始终按值返回,并且永远不会按const值返回。

答案 2 :(得分:0)

您可以通过以下方式更改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)因为它返回const std :: string是编译器(在我的情况下是GCC)   能够优化吗?

这种做法有一个基本原理,但它基本上是过时的(例如Herb Sutter建议为非原始类型返回const值。)

对于C ++ 11,强烈建议将值返回为非const,以便您可以充分利用 rvalue 引用。

关于此主题,您可以查看:

答案 3 :(得分:0)

一种可能性是做这样的事情:

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

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

如果您正在使用包含短字符串优化(例如,VC ++)的实现,那么很可能会大大提高速度(VC ++的快速测试显示它大约为4-5倍快)。

OTOH,如果您正在使用包含短字符串优化的实现,那么很可能它会基本上更慢。例如,使用g ++运行相同的测试,会产生大约慢4-5倍的代码。

还有一点:如果你的ID号可能超过4位数,这并没有给出相同的行为 - 它总是返回一个正好4个字符的字符串而不是stringstream代码创建的最少4个。如果你的身份证号码可能超过9999,那么这段代码根本不适合你。