我发现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字符串以避免深层复制?提前谢谢!
答案 0 :(得分:3)
std :: string不是最便宜的内存类。这使得使用它确实值得怀疑,尽管它还具有很好的API函数和很多安全性/可用性。
如果代码不是性能关键,请不要担心!在程序的整个生命周期中获得几微秒是不值得的。
相反,在您的代码上运行一个分析器并修复您可以找到的瓶颈。
假设您正在使用优化编译器并传递标志O2,O3 ......在很多情况下,编译器可以删除开销。如果需要,请在标题中实现For。如果您按值传递,请不要忘记std::move
。
在标准库的新标准中,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
无关,有时会分配以存储字符数据。
这只是内部数据成员的大小,这些是在堆栈上分配的(自动存储持续时间),这非常快。
堆栈分配通常不应该与您有任何关系。