void foo(T, size_t size)(in T[size] data){...}
//vs
void foo(T, size_t size)(const ref T[size] data){...}
根据https://stackoverflow.com/a/271344/944430,似乎在C ++中pass by value
在某些情况下会更快。
但D有一个特殊的关键字in
,我想知道何时应该使用它。 in
总是会产生副本还是编译器优化?
我可以遵循哪些指南来帮助我在const ref
和in
之间做出决定吗?
答案 0 :(得分:7)
我认为你不应该在函数参数上使用in
。 in
是来自D1的工件,它保持减少代码破坏但被更改为等同于const scope
。因此,每当您考虑在函数参数上键入in
时,请考虑const scope
,因为那是您真正要做的事情。并且scope
目前只对委托做任何事情,在这种情况下,它告诉编译器接受委托的函数不会返回它或将它分配给任何东西,因此不必关闭分配用于保存该委托的状态(因此,它在许多情况下提高了代表的效率),而对于所有其他类型,它完全被忽略,这意味着使用它是没有意义的(并且可能令人困惑),如果它曾经 对其他类型意味着什么(例如,有人建议它应该强制执行scope
传入的指针可以&#39 ; t转义函数),然后代码的语义可能会以意想不到的方式发生变化。据推测,当发生这种情况时,它会附带相应的警告,但为什么要用无意义的属性标记您的代码,这些属性可能在以后有意义,从而迫使您更改代码?此时,scope
应仅用于代表,因此in
只应用于代表,而您通常不需要const
个代表。所以,不要使用in
。
因此,最终,您真正要问的是,您应该使用const
还是const ref
。
简短的回答是,你通常不应该使用ref
,除非你想改变你传入的论点。我也会指出这个问题对于除了结构之外的任何东西都没有意义也许是静态数组,因为类已经是引用类型,并且没有任何内置类型(保存为静态数组)要花费很多东西来复制。答案越长......
移动语义构建在D中,所以如果你有一个函数可以通过值来获取它的参数 - 例如
auto foo(Bar bar) { ... }
然后它会移动参数,如果可以的话。如果你传递一个左值(一个值可以在赋值的左侧),那么该值将被复制,除非在编译器能够确定它可以优化副本的情况下(例如,在该函数调用之后从未使用变量时),但这取决于所使用的编译器和编译器标志。因此,通过值将变量传递给函数通常会产生副本。但是,如果您将函数传递给rvalue(无法在赋值的左侧执行的值),则它将移动该对象而不是复制它。这与C ++不同,其中移动语义直到C ++ 11才被引入,即使这样,它们也需要移动构造函数,而D使用postlbit构造函数,这会改变它以便默认情况下可以完成移动。之前的几个SO问题:
Does D have something akin to C++0x's move semantics?
Questions about postblit and move semantics
所以,是的,在D中有些情况下ref
传递会避免复制,但在D中,ref
总是需要左值(即使{{} 1}})。所以,如果你开始像你一样把const
放在C ++中ref const(T)
,那么你将会有许多非常讨厌的函数,因为每个临时都会必须先分配给变量才能调用该函数。因此,您应该认真考虑只使用const T&
来改变传入的变量,而不是效率。当然,您的默认值应该是ref
不能通过,但如果您确实需要额外的效率,则有两种选择:
const ref
- 上的函数,以便你有一个ref
的重载和一个const ref
的重载,以便左值传递给一个而不被复制,并且rvalues传递给另一个而不需要一个无关的变量。 e.g。ref
这有点令人讨厌但是当你只有 auto foo(const Bar bar) { foo(bar); }
auto foo(ref const(Bar) bar) { ... }
的一个参数时效果还不错。但是,随着更多ref
参数的添加,您会遇到组合的过载爆炸。 e.g。
ref
所以,这是有道理的,但并不是特别令人愉快。如果你像我在这里所做的那样定义重载,那么它也有一个缺点,即rvalues最终被传递给一个包装器函数(添加一个额外的函数调用 - 尽管它应该是非常有用的),这意味着他们&#39 ;现在由 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) { ... }
传递给主重载,如果其中一个参数传递给另一个函数或返回,编译器就不能进行移动,而如果ref
没有'参与其中,然后就可以了。这就是它现在认为你不应该像在C ++ 98中那样在C ++ 11中大量使用ref
的原因之一。
您可以通过复制每个重载的函数体来解决该问题,但这显然会产生维护问题以及创建代码膨胀。
const T&
,这基本上是为你做的,但函数必须是模板化的。 e.g。auto ref
所以,现在你只有一个重载,但是每次使用 auto foo()(const auto ref Bar bar, const auto ref Glop glop) { ... }
- ness的不同组合实例化模板时,它仍然会生成所有这些重载,并使用完整代码。所以,你的代码更清晰,但你仍然会变得更加臃肿,如果你需要使用虚函数来做这件事,那么你运气不好并且必须回到更明确的重载解决方案,因为模板函数不可能是虚拟的。
因此,一般情况下,出于效率原因,尝试让您的函数接受ref
会变得很难看。 D内置移动语义这一事实减少了对它的需求(就像使用C ++ 11一样,它现在认为,通过移动语义以及编译器如何优化它们,通过值传递通常会更好)。在一般情况下,在D中做到这一点非常丑陋,除非你真的得到一个重要的性能提升,否则为了提高效率,它可能不值得通过const ref
。您应该避免使用ref
来提高效率,除非您实际测量出了值得痛苦的性能差异。
要考虑的另一件事 - 与ref
分开 - 是因为D {'{1}}比C ++ ref
更具限制性(例如,丢弃const
并且变异是D中未定义的行为,而D const
是可传递的)。因此,在整个地方拍打const
有时会成为问题 - 特别是在通用代码中。因此,使用它可以很好地防止意外突变或指示一个函数不会改变它的论点,但不要轻易地把它打到任何不应该像在C ++中那样改变变量的地方。在有意义的地方使用它,但请注意,将遇到D' const
限制太多而无法使用的情况,即使C ++' s const
本来有用。
因此,在大多数情况下,当您希望函数采用const
时,您应默认采用普通const
。然后,如果您知道效率是一个问题,您可以考虑使用某种形式的T
(如果您需要T
或ref
不处理虚函数)。但默认不使用auto ref
。这样你的生活会更愉快。