string或const char *,哪一个更有效地用作构造函数参数

时间:2017-05-06 08:35:19

标签: c++

我发现sizeof(string)对于VC是28B而对于GCC是32B(不包括在堆上分配的数据部分)。这让我对使用string作为c ++构造函数的参数的效率持怀疑态度。所以我进行了一个小实验如下:

class Foo
{
    static int inc;
    int val;
    int id;
public:
    Foo(int value) :val(value), id(Foo::inc++) {
        std::cout << to_string() << " being constructed\n";
    }
    ~Foo() {
        std::cout << to_string() << " being destroyed\n";
    };
    Foo(Foo const &other) :Foo(other.val) {
        std::cout << other.to_string() << " being copied\n";
    }
    Foo& operator=(Foo const &other) {
        val = other.val;
        std::cout << other.to_string() << " being copied\n";
    }
    Foo(Foo &&other) :Foo(other.val) {
        std::cout << other.to_string() << " being moved\n";
    }
    Foo& operator=(Foo &&other) {
        val = other.val;
        std::cout << other.to_string() << " being moved\n";
    }
    int value() const noexcept { return val; }
    std::string to_string() const {
        return std::string("Foo_") + std::to_string(id) + "_(" + std::to_string(val) + ")";
    }

};
int Foo::inc = 0;


struct Bar {
    Bar(Foo const &foo) :val(foo) {}
    Bar(Foo &&foo) :val(std::move(foo)) {}
private:
    Foo val;
};

//------- run it ------
Bar bar {42};

这是我得到的结果:

Foo_0_(42) being constructed
Foo_1_(42) being constructed
Foo_0_(42) being moved
Foo_0_(42) being destroyed
Foo_1_(42) being destroyed

显然,临时Foo实例是在ctor的参数上创建的。所以我想当我将const char*传递给期望字符串的构造函数时,会发生相同的过程,i.d。移动/复制后,将立即创建并删除临时str> = 28/32字节。这种成本让我感到不舒服。不要误解我的意思,我会使用string作为班级的数据成员,它只是担心我的ctor的正式论证类型。

无论如何,如果我将string参数替换为const char*,假设我总是诉诸string::c_str(),我认为我永远不需要付出这样的代价,我会不会?我想听听你的观点。如果我的分析有任何问题,请指出。如果有一些机制可以消除临时字符串的成本,请教我如何工作。如果临时字符串不可避免,我是否必须使用const char*string&&重载ctor,一个用于避免临时字符串,一个用于rvalue字符串以避免深层复制?提前谢谢!

2 个答案:

答案 0 :(得分:3)

由于小对象优化,

std :: string不是最便宜的内存类。这使得使用它确实值得怀疑,尽管它还具有很好的API函数和很多安全性/可用性。

你要担心吗?

如果代码不是性能关键,请不要担心!在程序的整个生命周期中获得几微秒是不值得的。

相反,在您的代码上运行一个分析器并修复您可以找到的瓶颈。

按值/构造引用

传递std :: string

假设您正在使用优化编译器并传递标志O2,O3 ......在很多情况下,编译器可以删除开销。如果需要,请在标题中实现For。如果您按值传递,请不要忘记std::move

传递std :: string_view

在标准库的新标准中,string_view可用,如果没有,您可以轻松地从GSL复制它... 此类类似于原始字符指针(+ size),可用于需要传递字符串且需要std :: string

的地方

最后,如果你想存储字符串,你仍然需要转换为std ::。string

答案 1 :(得分:2)

请注意,您的问题明确提及构造函数参数,因此这个答案指的是那些。

其他推理适用于赋值运算符和setter。 (有关详细信息,请参阅this question

如果您要将字符串作为数据成员存储在对象中,则必然会发生字符串的创建。

因此,最简单的选择是将字符串的创建作为构造函数参数的副作用,然后移动到您的数据成员中。

在以下示例中,不会创建临时字符串然后销毁。所有分配的资源都存储在data_(通过移动)

class Foo
{
public:    
    Foo(std::string data)
        : data_(std::move(data))
    {}
private:
    std::string data_;
};

int main()
{
    const char* foo = "...";
    Foo a(foo);

    std::string bar = "...";
    Foo b(std::move(bar));

    Foo c("...");
}

sizeof分析而言,请注意此大小与动态(堆)存储std::string无关,有时会分配以存储字符数据。

这只是内部数据成员的大小,这些是在堆栈上分配的(自动存储持续时间),这非常快。

堆栈分配通常不应该与您有任何关系。