将const std :: string&作为参数传递的日子已经过去了吗?


604

我听取了香草萨特最近的谈话谁建议的理由来传递std::vectorstd::string利用const &在很大程度上消失了。他建议现在编写诸如以下的函数是可取的:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

我知道return_val在函数返回时,它将是一个右值,因此可以使用移动语义来返回,这非常便宜。但是,inval它仍然比引用(通常实现为指针)的大小大得多。这是因为a std::string具有各种组件,包括指向堆的指针和char[]用于短字符串优化的成员。因此在我看来,通过引用传递仍然是一个好主意。

谁能解释为什么Herb可能会这么说?


89
我认为该问题的最佳答案可能是阅读C ++ Next上有关 Dave Abrahams的文章。我要补充一点,我对此一无所知,认为这是题外话或没有建设性的。关于编程,这是一个明确的问题,对此有事实答案。
杰里·科芬

令人着迷,因此,如果您仍然必须进行复制,则按值传递可能比按引用传递更快。
2012年

3
@Sz。我很敏感地将问题错误地归类为重复问题并封闭。我不记得此案的细节,也没有对其进行重新审查。相反,我只是删除关于我犯错的假设的评论。谢谢您引起我的注意。
Howard Hinnant

2
@HowardHinnant,非常感谢,当您遇到如此专注和敏锐的感觉时,这总是一个珍贵的时刻,它是如此令人耳目一新!(当然,我会删除我的。)
Sz。

Answers:


393

赫伯之所以说他的话,是因为这样的情况。

假设我有A调用function的函数B,调用function的函数C。并A通过一个字符串B并进入CA不知道或不在乎C; 所有人都A知道B。也就是说,C是的实现细节B

假设A定义如下:

void A()
{
  B("value");
}

如果B和C接受字符串by const&,则它看起来像这样:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

一切都很好。您只是传递指针,不复制,不移动,每个人都很高兴。C需要一个,const&因为它不存储字符串。它只是使用它。

现在,我想做一个简单的更改:C需要将字符串存储在某个地方。

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

您好,请复制构造函数和潜在的内存分配(忽略短字符串优化(SSO))。C ++ 11的移动语义应该可以消除不必要的复制构造,对吗?并A通过临时;没有理由为什么C必须复制数据。它应该潜移默化。

除了它不能。因为需要一个const&

如果我更改C为按值获取其参数,那只会导致B将其复制到该参数中。我什么都没有。

因此,如果我只是str通过所有函数按值传递std::move数据,并依赖于对数据进行随机排序,那么我们就不会遇到这个问题。如果有人想坚持下去,他们可以。如果他们不这样做,那就好。

更贵吗?是; 转化为价值比使用引用要昂贵得多。它比副本便宜吗?不适用于带有SSO的小字符串。这值得吗?

这取决于您的用例。您讨厌多少内存分配?


2
当您说移入一个值比使用引用更昂贵时,以不变的数量(与要移动的字符串的长度无关)还是更昂贵的,对吗?
Neil G

3
@NeilG:您了解“依赖于实现”的含义吗?您说的是错误的,因为这取决于是否以及如何实施SSO。
ildjarn

17
@ildjarn:在阶次分析中,如果某个事物的最坏情况受常数约束,那么它仍然是恒定时间。没有最长的小绳子吗?该字符串是否需要花费一定的时间进行复制?并非所有较小的字符串都花费较少的时间进行复制?然后,在顺序分析中,小字符串的字符串复制是“恒定时间”,尽管小字符串需要花费不同的时间进行复制。阶次分析与渐近行为有关
尼尔·G

8
@NeilG:当然可以,但是您最初的问题是“ 以不变的数量(与要移动的字符串的长度无关),它仍然会更昂贵吗? ”我要说明的是,不同的方法可能会更昂贵常数取决于字符串的长度,总和为“否”。
ildjarn 2012年

13
为什么moved按值将字符串从B到C?如果B是B(std::string b)C,C(std::string c)那么我们要么调用C(std::move(b))B要么b必须保持不变(因此从“移出”)直到退出B。(如果b在调用之后未使用,也许优化的编译器将根据as-if规则移动字符串,但我认为没有强有力的保证。)strto 的副本也是如此m_str。即使使用rvalue初始化了函数参数,它也是函数内部的一个左值,并且std::move需要从该左值移出。
Pixelchemist

163

将const std :: string&作为参数传递的日子已经过去了吗?

没有。许多人将建议(包括Dave Abrahams)应用到了它所适用的领域之外,并简化了它以适用于所有 std::string参数-对于所有参数和应用程序,始终std::string按值传递不是“最佳实践”,因为优化这些讨论/文章只针对少数情况下的适用

如果要返回值,更改参数或获取值,则按值传递可以节省昂贵的复制并提供语法上的便利。

与以往一样,通过const引用传递可在不需要副本时节省大量复制。

现在来看具体示例:

但是inval仍然比引用(通常实现为指针)的大小大很多。这是因为std :: string具有各种组件,包括指向堆的指针和用于短字符串优化的成员char []。因此在我看来,通过引用传递仍然是一个好主意。谁能解释为什么Herb可能会这么说?

如果需要考虑堆栈大小(并且未对其进行内联/优化),则return_val+ inval> return_val-IOW,可通过在此处传递值来减少峰值堆栈使用量(注意:ABI的过度简化)。同时,通过const引用传递可能会禁用优化。这里的主要原因不是要避免堆栈增长,而是要确保在适用的情况下可以执行优化。

通过const引用传递的日子还没有结束-规则比过去更加复杂。如果性能很重要,您将明智地根据实现中使用的详细信息考虑如何传递这些类型。


3
对于堆栈使用情况,典型的ABI将在没有堆栈使用情况的情况下在寄存器中传递单个引用。
ahcox 2015年

63

这在很大程度上取决于编译器的实现。

但是,这也取决于您使用什么。

让我们考虑下一个功能:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

为了避免内联,这些功能在单独的编译单元中实现。然后:
1.如果将文字传递给这两个函数,则性能不会有太大差异。在这两种情况下,都必须创建一个字符串对象
2。如果传递另一个std :: string对象,foo2它将胜过foo1,因为foo1它将进行深层复制。

在我的PC上,使用g ++ 4.6.1,我得到了以下结果:

  • 参考变量:1000000000次迭代->经过的时间:2.25912秒
  • 按值可变:1000000000次迭代->经过的时间:27.2259秒
  • 参考值字面值:100000000次迭代->经过的时间:9.10319秒
  • 按值的原义值:100000000次迭代->经过的时间:8.62659秒

4
更相关的是函数内部发生的事情:如果通过引用调用它,是否需要在内部制作一个副本,当按值传递时可以将其忽略吗?
leftaboutabout'4

1
@leftaroundabout是的,偏离路线。我以为这两个功能都在做完全相同的事情。
BЈовић

5
那不是我的意思。通过值传递还是通过引用传递更好,取决于您在函数内部执行的操作。在您的示例中,实际上并没有使用太多的字符串对象,因此引用显然更好。但是,如果函数的任务是将字符串放置在某种结构中或执行某种涉及字符串多次拆分的递归算法,则与按引用传递相比,按值传递实际上可以节省一些复制。Nicol Bolas很好地解释了这一点。
leftaboutabout

3
对我来说,“这取决于您在函数内部所做的事情”是不好的设计-因为您将函数的签名基于实现的内部。
汉斯·奥尔森,2016年

1
可能是拼写错误,但最后两个文字计时的循环次数减少了10倍。
TankorSmash

54

简短的回答:不!长答案:

  • 如果您不修改字符串(处理方式为只读),请将其作为传递const ref&
    const ref&显然,使用它的函数执行时需要保持在范围内)
  • 如果您打算对其进行修改,或者知道它将超出范围(线程),请将其作为传递value,请勿const ref&在函数体内复制它。

cpp-next.com上有一名为“希望速度,通过价值传递!”的帖子。TL; DR:

准则:请勿复制函数参数。而是按值传递它们,然后让编译器进行复制。

^的翻译

不要复制函数参数 ---表示:如果您打算通过将参数值复制到内部变量来修改参数值,则只需使用value参数即可

所以,不要这样做

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

这样做

std::string function(std::string aString){
    aString.clear();
    return aString;
}

需要修改函数主体中的参数值时。

您只需要知道如何计划在函数主体中使用参数即可。只读或否...,如果它在范围内。


2
在某些情况下,建议您通过引用传递,但您指向的准则是建议始终按值传递。
基思·汤普森

3
@KeithThompson 不要复制函数参数。表示请勿将拷贝const ref&到内部变量以对其进行修改。如果需要修改...,请将参数设为值。对于我的非英语口语自我来说,这很清楚。
CodeAngry

5
@KeithThompson 准则引号(不要复制您的函数参数。而是按值传递它们,让编译器进行复制。)从该页面复制。如果这还不够清楚,我将无能为力。我不完全相信编译器会做出最佳选择。我宁愿明确定义函数参数的方式。#1如果为只读,则为const ref&。#2如果我需要编写它,或者我知道它超出范围...我使用一个值。#3如果需要修改原始值,请通过ref&。#4 pointers *如果参数是可选的,那么我可以使用nullptr它。
CodeAngry

11
我不是站在通过价值还是通过引用的问题上。我的观点是,在某些情况下,您主张通过引用传递,但随后引用(似乎是为了支持您的立场)建议始终采用价值传递的准则。如果您不同意该准则,则可能要这样说并解释原因。(指向cpp-next.com的链接对我不起作用。)
Keith Thompson,

4
@KeithThompson:您误解了准则。它不是“总是”按值传递。总而言之,它是“如果要创建本地副本,请使用按值传递来让编译器为您执行该副本。” 这并不是说当您不打算复制时使用按值传递。
Ben Voigt 2015年

43

除非您确实需要一份副本,否则还是可以接受的const &。例如:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

如果将其更改为按值获取字符串,则最终将移动或复制参数,而无需这样做。复制/移动不仅可能更昂贵,而且还带来了新的潜在故障;复制/移动可能会引发异常(例如,复制期间的分配可能会失败),而引用现有值则不会。

如果确实需要副本,那么按值传递和返回通常(总是?)是最佳选择。实际上,除非您发现多余的副本实际上会导致性能问题,否则我通常不会在C ++ 03中担心它。在现代编译器上,复制省略似乎相当可靠。我认为,如今人们大多已经过时了,人们一直怀疑您必须检查编译器对RVO的支持表。


简而言之,C ++ 11在这方面并没有真正改变任何东西,除了那些不信任复制省略的人。


2
Move构造函数通常使用来实现noexcept,但复制构造函数显然不是。
leftaboutabout'4

25

几乎。

在C ++ 17中,我们有了basic_string_view<?>,这使我们基本上可以得出一个狭窄的std::string const&参数用例。

移动语义的存在消除了一种用例std::string const&-如果您打算存储参数,则按std::string值取值是最佳的,因为可以move从参数中取出。

如果有人用原始C调用了您的函数,则"string"意味着仅std::string分配了一个缓冲区,而不是两个缓冲区std::string const&

但是,如果您不打算进行复制,std::string const&在C ++ 14中,“ by by” 仍然有用。

使用std::string_view,只要您不将上述字符串传递给期望使用C样式'\0'终止的字符缓冲区的API,就可以更有效地获得std::string类似的功能,而无需承担任何分配的风险。甚至在std::string_view没有任何分配或字符复制的情况下,原始C字符串甚至可以变成a 。

那时,std::string const&当您不复制数据批发,而是将其传递给期望空终止缓冲区为C风格的API,并且需要std::string提供更高级别的字符串函数时,它的用途是。实际上,这是一组罕见的要求。


2
我很欣赏这个答案-但我想指出的是,它确实受到了某些特定领域的偏见的困扰(就像许多质量答案一样)。断言:“在实践中,这是一组罕见的要求”……在我自己的开发经验中,这些约束(对于作者来说似乎异常狭窄)几乎一直都得到满足。值得指出这一点。
fish2000

1
@ fish2000要清楚,std::string要统治您,您不仅需要其中一些要求,还需要所有这些要求。我承认,其中任何一个甚至两个都是很普遍的。也许您通常确实需要全部3个(例如,您正在对字符串参数进行一些解析,以选择要将其批发地传递给哪个C API?)
Yakk-Adam Nevraumont

@ Yakk-AdamNevraumont这是YMMV的事情–但如果(例如)您正在针对POSIX或其他C字符串语义是最低公分母的API进行编程,则这是一个经常使用的案例。我应该说我真的很喜欢std::string_view–正如您所指出的那样,“即使没有任何分配或字符复制,原始的C字符串甚至可以变成std :: string_view”,对于那些在C ++上下文中使用C ++的人来说,这一点值得记住确实如此。
fish2000

1
@ fish2000“'一个原始C字符串甚至可以被转换为std :: string_view而无需任何分配或字符复制',这值得记住”。的确,但是它遗漏了最好的部分-在原始字符串是字符串文字的情况下,它甚至不需要运行时strlen()
Don Hatch

17

std::string不是Plain Old Data(POD),并且它的原始大小也不是最相关的东西。例如,如果传入的字符串大于SSO的长度并在堆上分配,则我希望复制构造函数不会复制SSO存储。

推荐这样做的原因是因为它inval是根据参数表达式构造的,因此始终可以适当地移动或复制-假设您需要参数的所有权,则不会造成性能损失。如果您不这样做,那么const引用仍然是更好的选择。


2
有趣的一点是,复制构造函数足够聪明,如果不用SSO,不必担心它。可能是正确的,我将不得不检查是否正确;-)
Benj 2012年

3
@Benj:我知道老评论,但是如果SSO足够小,则无条件复制比执行条件分支要快。例如,64字节是高速缓存行,可以在非常短的时间内复制。在x86_64上大约8个周期或更少。
Zan Lynx

即使复制构造函数未复制SSO std::string<>,也会从堆栈中分配32个字节,其中16个字节需要初始化。将其与仅分配并初始化的8个字节进行比较,以供参考:这是CPU工作量的两倍,并且占用的缓存空间是其他数据无法使用的四倍。
cmaster-恢复莫妮卡

哦,我忘了谈论在寄存器中传递函数参数的问题。这会使最后一个被调用方的引用的堆栈使用率降低到零...
cmaster-恢复莫妮卡

16

我已经在此处复制/粘贴了该问题的答案,并更改了名称和拼写以适合该问题。

这是测量要求的代码:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

对我来说,输出:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

下表总结了我的结果(使用clang -std = c ++ 11)。第一个数字是复制结构的数量,第二个数字是移动结构的数量:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

按值传递解决方案仅需要一个重载,但在传递左值和x值时会花费额外的移动构造。对于任何给定情况,这可能是可接受的,也可能是不可接受的。两种解决方案都有优点和缺点。


1
std :: string是标准库类。它既可以移动又可以复制。我看不出这有什么关系。OP正在询问更多有关移动与引用的性能,而不是有关移动与复制的性能。
Nicol Bolas 2012年

3
这个答案计算了移动的次数,并复制了在Herb和Dave所描述的按值传递设计下std​​ :: string所经历的过程,而不是通过引用与一对重载函数进行传递。我在演示中使用OP的代码,除了在复制/移动它时替换为虚拟字符串以大喊大叫之外。
Howard Hinnant 2012年

您可能应该在执行测试之前优化代码……
顺磁牛角包2014年

3
@TheParamagneticCroissant:您得到了不同的结果吗?如果是这样,使用带有哪些命令行参数的编译器?
Howard Hinnant 2014年

14

在推荐const std::string&使用参数类型时,Herb Sutter和Bjarne Stroustroup一起仍在记录中。参见https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in

有一个陷阱在这里的其他任何答案中都没有提及:如果将字符串文字传递给const std::string&参数,它将传递对临时字符串的引用,该临时字符串是在运行时创建的以保存文字字符。如果您随后保存该引用,则在释放临时字符串后,该引用将无效。为了安全起见,您必须保存一个副本,而不是参考。问题源于字符串文字是const char[N]类型,需要提升为的事实std::string

下面的代码说明了陷阱和解决方法,以及次要的效率选项- const char*方法的重载,如是否有方法传递字符串文字作为C ++中的引用所述

(注意:Sutter&Stroustroup建议,如果保留字符串的副本,还请提供带有&&参数和std :: move()的重载函数。)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

输出:

const char * constructor
const std::string& constructor

Second string
Second string

这是一个不同的问题,并且WidgetBadRef不需要具有const&参数就可以出错。问题是,如果WidgetSafeCopy仅使用字符串参数会更慢吗?(我认为临时复制给成员肯定更容易发现)
Superfly Jon

请注意,这T&&并不总是通用的参考;实际上,std::string&&它将始终是右值引用,而永远不会是通用引用,因为不会执行任何类型推导。因此,Stroustroup&Sutter的建议与Meyers的建议不冲突。
贾斯汀时间-恢复莫妮卡

@JustinTime:谢谢;我删除了错误的最后一句,声称实际上std :: string &&是通用参考。
circlepi314 '17

@ circlepi314不客气。这很容易混淆,有时给定的T&&是推导的通用引用还是未推导的右值引用都可能会造成混淆。它可能会一直清晰的,如果他们引入了通用引用不同的符号(例如&&&,作为一个组合&&&),但这可能只是看傻了。
贾斯汀时间-恢复莫妮卡的时间

8

使用C ++参考的IMO std::string是一种快速且简短的局部优化,而使用按值传递可能(也可能不是)更好的全局优化。

因此答案是:它取决于情况:

  1. 如果您从外部到内部函数都编写了所有代码,则知道代码的作用,可以使用reference const std::string &
  2. 如果您编写库代码或在传递字符串的地方使用大量库代码,则可以通过信任std::string复制构造函数的行为在全局意义上获得更多收益。

6

请参见“香草萨特“回到现代C ++风格的基础!纲要”。在其他议题,他回顾了进来与C ++ 11,特别着眼于该公司在过去被赋予了参数传递的建议和新思路通过值传递字符串的想法。

幻灯片24

基准测试表明std::string,在函数无论如何都要复制s的情况下,按值传递s的速度可能会明显变慢!

这是因为您强迫它始终制作完整副本(然后移到适当位置),而该const&版本将更新旧字符串,这可能会重用已分配的缓冲区。

参见他的幻灯片27:对于“设置”功能,选项1始终相同。选项2添加了一个重载以供右值引用,但是如果有多个参数,这将产生组合爆炸。

仅对于必须创建字符串(不更改其现有值)的“接收器”参数,传递值技巧才有效。也就是说,其中参数直接初始化匹配类型成员的构造函数

如果您想了解对此有多深的忧虑,请观看Nicolai Josuttis的介绍,并为此感到好运(“ Perfect – Done!”( n 完美–完成!)在发现先前版本的错误后n次。去过那里吗?)


标准指南中也将其汇总为F.15


3

正如@JDługosz在评论中指出的那样,Herb在另一个(后来?)演讲中提出了其他建议,请大致从此处查看:https ://youtu.be/xnqTKD8uD64?t=54m50s 。

他的建议可以归结为仅对函数使用值参数 f接收器参数,假定您将构造从这些接收器参数中移出。

f与分别针对左值和右值参数量身定制的最佳实现相比,这种通用方法仅增加了左值和右值参数的移动构造函数的开销。为了了解为什么会发生这种情况,假设f有一个value参数,其中T有一些复制并移动可构造类型:

void f(T x) {
  T y{std::move(x)};
}

f用左值参数调用将导致复制构造函数被调用来构造x,而移动构造函数被调用来构造y。另一方面,f使用rvalue参数调用将导致调用move构造函数进行构造x,而另一个move构造函数来构造y

通常,ffor左值参数的最佳实现如下:

void f(const T& x) {
  T y{x};
}

在这种情况下,仅调用一个副本构造函数来构造y。通常,ffor rvalue参数的最佳实现如下:

void f(T&& x) {
  T y{std::move(x)};
}

在这种情况下,只有一个move构造函数被调用来构造y

因此,明智的折衷办法是采用一个值参数,并针对最佳实现对lvalue或rvalue参数进行一个额外的move构造函数调用,这也是Herb演讲中给出的建议。

正如@JDługosz在评论中指出的那样,按值传递仅对将从接收器参数构造某些对象的函数有意义。当您有一个f复制其参数的函数时,按值传递方法将比一般的按常量引用引用方法具有更多的开销。f保留其参数副本的函数的按值传递方法将具有以下形式:

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

在这种情况下,有一个复制构造和一个左值参数的移动分配,以及一个复制结构和一个右值参数的移动分配。左值参数的最佳情况是:

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

归结为仅是一个赋值,它可能比按值传递方法所需的复制构造函数和移动赋值便宜得多。这样做的原因是,该分配可能会重复使用中现有的已分配内存y,从而防止(取消)分配,而复制构造函数通常会分配内存。

对于右值参数f,保留副本的最佳实现形式为:

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

因此,在这种情况下,只有一个移动分配。将右值传递给f采用const引用的版本时,只需花费一个分配,而不是移动分配。因此相对而言,f在这种情况下采用const引用作为一般实现的版本是可取的。

因此,总的来说,对于最佳的实现,您将需要重载或进行某种完美的转发,如演讲中所示。缺点是需要重载的数量激增,具体取决于参数的数量f,以防万一您选择对参数的值类别重载。完美转发的缺点是f变成了模板功能,从而阻止了它的虚拟化,并且如果您想100%正确地使用它,则会导致代码复杂得多(有关详细信息,请参见讨论)。


请参阅我的新答案中的Herb Sutter的发现:仅当您移动结构而不是移动分配时才这样做。
JDługosz

1
@JDługosz,感谢您提供指向Herb演讲的指导,我只是看着它并完全修改了我的回答。我不知道(移动)分配建议。
Ton van den Heuvel,

该数字和建议现在在标准指南文档中
JDługosz

1

问题在于“ const”是非粒度限定词。通常,“常量字符串引用”的意思是“不要修改此字符串”,而不是“不要修改引用计数”。在C ++中,根本无法说出哪些成员是“ const”。它们要么都是,要么都不是。

为了破解解决此问题的语言,STL 可以让“C()”在你的例子作出的举动语义副本反正,和尽职尽责地忽略了“常量”关于引用计数(可变)。只要明确指定,就可以了。

由于STL没有,我有一个字符串版本,该字符串将const_casts <>移出引用计数器(无法追溯地使类层次结构中的变量可变),并且-瞧,您可以自由地将cmstring传递为const引用,并一整天都在无泄漏或无问题的情况下复制它们的深层功能。

由于C ++在此不提供“派生类const粒度”,因此编写一个好的规范并创建一个闪亮的新的“ const可移动字符串”(cmstring)对象是我所见过的最好的解决方案。


@BenVoigt是的...它应该是可变的,而不是抛弃它,但是您不能将STL成员更改为在派生类中可变的。
Erik Aronesty
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.