我怎么知道何时使用`const ref`或`in`?

Mai*_*ein 6 d

void foo(T, size_t size)(in T[size] data){...}
//vs
void foo(T, size_t size)(const ref T[size] data){...}
Run Code Online (Sandbox Code Playgroud)

根据/sf/answers/18994111/,pass by value在某些情况下,C++似乎更快.

但是D有一个特殊的关键字in,我想知道什么时候应该使用它.是否in总是导致一个副本还是一个编译器优化?

我可以遵循哪些指导方针帮助我决定const refin

Jon*_*vis 7

我认为你永远不应该使用in函数参数.in是来自D1的一个工件,它保持减少代码破坏但被改为等同于const scope.所以,每当你想到in在函数参数上打字时,请想一想const scope,因为那就是你真正在做的事情.并且scope目前只对委托做任何事情,在这种情况下,它告诉编译器接受委托的函数不会返回它或将其分配给任何东西,因此不必分配闭包来保存该委托的状态(因此,它在许多情况下提高了代表的效率),而对于所有其他类型,它完全被忽略,这意味着使用它是毫无意义的(并且可能令人困惑),并且如果它确实对其他类型意味着其他类型(例如有人建议它应该强制执行一个传入的指针,因为scope它无法转义函数),那么代码的语义可能会以意想不到的方式发生变化.据推测,当发生这种情况时,它会附带适当的警告,但是为什么要用无意义的属性标记你的代码,这些属性可能在以后有意义,从而迫使你改变你的代码?此时,scope应仅用于代理,因此in只应在代理上使用,并且通常不需要const委托.所以,就是不要使用in.

所以,最终,你真正要问的是你是否应该使用constconst ref.

简短的回答是你通常不应该使用,ref除非你想改变你传入的参数.我也会指出这个问题对于除了结构和静态数组之外的任何东西都没有意义,因为类已经是引用类型,并且没有任何内置类型(保存为静态数组)花费很多东西来复制.答案越长......

移动语义被内置到D中,所以如果你有一个函数通过值获取它的参数 - 例如

auto foo(Bar bar) { ... }
Run Code Online (Sandbox Code Playgroud)

如果可以的话,它会移动参数.如果你传递一个左值(一个值可以在赋值的左侧),那么该值将被复制,除非在编译器能够确定它可以优化副本的情况下(例如,在该函数调用之后从未使用变量),但这将取决于所使用的编译器和编译器标志.因此,通过值将变量传递给函数通常会产生副本.但是,如果您将函数传递给rvalue(不能在赋值的左侧进行的值),那么它将移动该对象而不是复制它.这与C++不同,其中移动语义直到C++ 11才被引入,即使这样,它们也需要移动构造函数,而D使用postlbit构造函数,这会改变它以便默认情况下可以完成移动.以前的几个SO问题:

D有类似于C++ 0x的移动语义吗?
关于postblit和移动语义的问题

所以,是的,在D中有些情况下,传递ref将避免复制,但在D中,ref 总是需要左值(即使有const).所以,如果你ref const(T)const T&C++ 一样开始到处运行,那么你将会遇到许多非常烦人的函数,因为每个临时函数都必须先分配给变量来调用函数.所以,你应该认真考虑只ref在你想要改变传入的变量而不是效率时使用.当然,您的默认值应该是不通过const ref,但如果您确实需要额外的效率,则有两种选择:

  1. ref-ness 上重载函数,以便你有一个需要的重载const ref和一个需要的重载,ref以便将左值传递给一个而不被复制,并且rvalues传递给另一个而不需要一个无关的变量.例如
    auto foo(const Bar bar) { foo(bar); }
    auto foo(ref const(Bar) bar) { ... }
Run Code Online (Sandbox Code Playgroud)

这有点烦人,但只有一个参数时效果很好ref.但是,随着更多ref参数的添加,您会遇到过载的组合爆炸.例如

    auto foo(const Bar bar, const Glop glop) { foo(bar, glop); }
    auto foo(ref const(Bar) bar, const Glop glop) { foo(bar, glop); }
    auto foo(const Bar bar, ref const(Glop) glop) { foo(bar, glop); }
    auto foo(ref const(Bar) bar, ref const(Glop) glop) { ... }
Run Code Online (Sandbox Code Playgroud)

所以,这是有道理的,但并不是特别令人愉快.如果你像我在这里所做的那样定义重载,那么它也有一个缺点,即rvalues最终被传递给一个包装器函数(添加一个额外的函数调用 - 虽然应该是一个非常可用的),这意味着它们是现在传递ref给主重载,如果其中一个参数传递给另一个函数或返回,编译器不能进行移动,而如果ref没有涉及,那么它可以有.这就是它现在认为你不应该const T&像在C++ 98中那样在C++ 11中大量使用的原因之一.

您可以通过复制每个重载的函数体来解决该问题,但这显然会产生维护问题以及创建代码膨胀.

  1. 另一种方法是使用auto ref,它基本上是为你做的,但函数必须是模板化的.例如
    auto foo()(const auto ref Bar bar, const auto ref Glop glop) { ... }
Run Code Online (Sandbox Code Playgroud)

所以,现在你只有一个重载,但是每次使用ref-ness 的不同组合实例化模板时,它仍然会生成所有这些重载,并使用完整代码.所以,你的代码更清晰,但是你仍然会变得更加膨胀,如果你需要使用虚函数来做这件事,那么你运气不好并且必须回到更明确的重载解决方案,因为模板化的函数可以'是虚拟的.

所以,一般来说,试图让你的函数接受const ref效率的原因只是变得丑陋.D内置移动语义的事实减少了对它的需求(就像使用C++ 11一样,现在认为通过移动语义以及编译器如何优化它们,传递值通常更好).在一般情况下,在D中做到这一点很丑陋,除非你真的得到一个重要的性能提升,否则ref为了提高效率可能不值得过去.你可能应该避免使用ref效率,除非你真的测量了值得痛苦的性能差异.

要考虑的另一件事 - 与ref-ness 分开- 是D const比C++更具限制性const(例如,丢弃const和变异是D中未定义的行为,而D是const可传递的).因此,const在整个地方拍打有时会成为问题 - 特别是在通用代码中.因此,使用它可以很好地防止意外突变或指示一个函数不会改变它的参数,但不要只是轻率地将它打到不应该像在C++中那样改变变量的地方.在有意义的地方使用它,但要注意你遇到D const限制太强而无法使用的情况,即使C++有用const.

因此,在大多数情况下,当你希望你的函数采用a时T,你应该默认使用它T.然后,如果你知道效率是一个问题,你可以考虑使用某种形式ref(可能有利于auto refconst auto ref如果你没有处理虚函数).但默认不使用ref.这样你的生活会更愉快.