我是C ++的新手,当我应该使用指针,引用,std :: move时,仍然很难理解。我已经编写了一个短函数来使用定界符分割字符串。
std::vector<std::string> mylib::split(std::string string, char delimiter) {
std::vector<std::string> result = std::vector<std::string>();
std::string cache = std::string();
cache.reserve(string.size());
for (char c : string) {
if (c == delimiter) {
result.push_back(std::string(cache));
cache.clear();
} else {
cache += c;
}
}
cache.shrink_to_fit();
result.push_back(cache);
return result;
}
我对此功能有一些疑问: 我应该使用
std::vector<std::string> mylib::split(std::string string, char delimiter) {
或
std::vector<std::string> mylib::split(std::string &string, char delimiter) {
应该是
result.push_back(std::string(cache));
或
result.push_back(std::move(std::string(cache)));
我是否必须关心任何使用过的对象的破坏,还是可以像这样使用该功能? 另外,如果还有其他方法可以改进此方法,我很高兴听到您的想法。
答案 0 :(得分:2)
最好的是
std::vector<std::string> mylib::split(const std::string &string, char delimiter) {
因为您不会复制超过所需的内容,并且可以保证不会给呼叫者带来影响,所以您不会修改他们的字符串。并且使API的意图更加清晰。
result.push_back(std :: move(std :: string(cache)));
IMO(并非所有人都会同意),您不必担心std :: move'ing the string。是的,您可以这样做,因为在两种情况下都不会使用缓存(无论如何也不会清除缓存)。仅当性能成为问题时才应开始护理。而且由于您是逐个复制char,所以我怀疑最高的性能改进将来自移动语义。
如所讨论的那样,删除初始化程序并使用令牌副本:
std::vector<std::string> split(const std::string& string, char delimiter)
{
std::vector<std::string> result;
size_t pos = 0;
for (size_t scan = 0; scan < string.size(); ++scan)
{
if (string[scan] == delimiter)
{
result.push_back(string.substr(pos, scan - pos));
pos = scan + 1;
}
}
result.push_back(string.substr(pos, string.size() - pos));
return result;
}
答案 1 :(得分:1)
经验法则是:
当您要表示函数可以修改参数并且更改应该在外部可见时,请使用&
。通过&
传递参数也不会创建副本。
如果要表示该函数将不修改该对象,请使用const
。虽然会复制它。
使用const &
来组合以上两种情况:该对象不会被该函数修改,也不会被复制(这在复制很昂贵时很重要,例如字符串)。 / p>
因此对您来说,最佳解决方案是:使用const std::string& value
(请更改变量的名称)。您无需修改字符串,它可能太大而无法复制。
关于std::move
。它的作用是(基本上)将非临时对象变为临时对象。因此,如您所见,对临时对象(您的情况)使用std::move
是没有意义的。
我们为什么要这样做?为了允许C ++编译器进行积极的优化。考虑以下代码:
std::string text = "abcd";
result.push_back(text);
C ++不知道将不再使用text
。因此它必须复制它。但是有了这个:
std::string text = "abcd";
result.push_back(std::move(text));
您告诉C ++编译器:“嘿,我不再使用text
变量,因此您不必复制它,只需将其内部移动到向量上即可。而且您所需要知道的是,在字符串复制的情况下(线性复杂度)比移动(总是恒定时间)更昂贵。
警告-收到意见:我发现std::move
这个名字确实令人困惑。它实际上并没有移动任何东西。这只是静态转换。为什么不叫它std::cast_to_temp
呢?
无论如何,此result.push_back(std::move(std::string(cache)));
是错误的。无意义。您不会避免复制,并且std::move
不会执行任何操作。但是这个result.push_back(std::move(cache));
确实有意义。但是必须进行仔细分析:此后真的不需要cache
吗?看起来是这样(尽管我没有深入研究您的代码)。
最后,您只在构造时会担心破坏,即对于每个new
,您都需要一个delete
。您没有new
,也不需要delete
*。
*并不总是正确的,有时您要处理一个讨厌的代码,该代码为您执行隐式,不可见的new
,但实际上迫使您执行delete
。是的,有时候很难。但是AFAIK不会在标准(或任何其他自重)库中发生。这是非常不好的做法。
最后的提示:当然,这是C ++,实际上一切都更加复杂,每个规则都有例外,依此类推。但是暂时不要担心细节,可以逐步学习。
答案 2 :(得分:1)
按值或引用传递:
这将创建字符串的副本:
std::vector<std::string> mylib::split(std::string string, char delimiter)
这将传递对字符串的引用:
std::vector<std::string> mylib::split(std::string &string, char delimiter)
在上述情况下,您宁愿传递引用,因为您返回一个std :: vector,并且仅使用字符串读取字符串的一部分以将其推入向量。现在,因为您只阅读了它,所以最好将它设置为const:
std::vector<std::string> mylib::split(const std::string &string, char delimiter)
然后,您可以100%确保为拆分函数提供的变量保持不变。想象一下:
std::string string = "some,values";
如果您传递字符串以按值分割:
std::vector<std::string> mylib::split(std::string string, char delimiter) {
string = "something else";
...
}
调用split之后,您将读取字符串变量:
std::cout << string << std::endl;
这将打印“一些值”。
但是,如果您通过引用:
std::vector<std::string> mylib::split(std::string &string, char delimiter) {
string = "something else";
}
它将打印“其他内容”,基本上是您修改了真实字符串。
如果将其设为const,则编译器将不允许您覆盖split函数中的字符串。因此,除非需要在函数中更改变量,否则请向其传递const引用。
移动或复制:
这将创建字符串的副本:
result.push_back(std::string(cache));
这将移动缓存的内容。
result.push_back(std::move(cache));
如果您知道创建副本通常比搬走东西要花更多的钱,那么您就会知道搬迁会更有效率,即更快。但是再说一次,为字符串添加移动调用听起来像过早的优化。除非您要处理大量数据,否则我看不到要移动字符串而不是复制字符串的原因,因为这会使代码的可读性降低,并且性能提升可能很小。
指针与参考
基本上,您可以像参考引用一样思考指针。这是一段内存的地址。语法不同,指针不能为null,而引用不能为null。指针也可以在堆上分配,而引用总是在堆上分配。
std::string string = "some,values";
std::vector<std::string> mylib::split(std::string *string, char delimiter) {
*string = "something else";
...
}
std::cout << *string << std::endl; // will print "something else"
std::cout << string << std::endl; // will print the address of the pointer
请注意,拆分中的*表示您传递了一个指针,字符串'* string =“ something else”'之前的*表示该指针已取消引用,并且该值已写入该指针的位置。与打印相同,我们读取值并通过解引用指针进行打印。
我希望这可以消除您的疑问。
答案 3 :(得分:0)
您应该阅读有关C ++通过引用与按值传递的更多信息。但为了简单起见,
std::vector<std::string> mylib::split(std::string string, char delimiter) {
。这意味着您按值传递字符串对象,并在该字符串的函数内进行复制。 std::vector<std::string> mylib::split(std::string &string, char delimiter) {
表示您正在通过引用传递字符串对象。因此,当您在函数中更改字符串时,将在声明字符串的位置独立地更改字符串本身。另外,由于不必复制对象,因此按引用对pas的性能更友好。我是否必须关心任何使用过的对象的破坏,还是可以像那样使用此功能?
不,您不必担心损坏任何对象,因为您仅使用STL而不是用户定义的对象。此外,应该像这样:result.push_back(std::string(cache))
。将对象推入容器时,请勿使用std::move
。