我怎么知道什么时候使用 `const ref` 或 `in`?
How do I know when to use `const ref` or `in`?
void foo(T, size_t size)(in T[size] data){...}
//vs
void foo(T, size_t size)(const ref T[size] data){...}
根据 看来,在 C++ 中 pass by value
在某些情况下可能更快。
但是D有一个特殊的关键字in
,我不知道什么时候应该使用它。 in
总是生成副本还是编译器优化?
是否有任何准则可以帮助我在 const ref
和 in
之间做出选择?
我认为你不应该在函数参数上使用 in
。 in
是 D1 中的一个工件,它被保留以减少代码损坏,但被更改为等同于 const scope
。因此,每当您想到在函数参数上键入 in
时,请想到 const scope
,因为这才是您真正要做的。 scope
目前只对委托做任何事情,在这种情况下,它告诉编译器接受委托的函数不会 return 它或将它分配给任何东西,因此不必关闭分配以保存该委托的状态(因此,它在许多情况下提高了委托的效率),而对于所有其他类型,它被完全忽略,这意味着使用它是没有意义的(并且可能会造成混淆),如果它曾经 does 对其他类型有其他含义(例如,有人建议它应该强制作为 scope
传入的指针不能转义函数),那么语义您的代码可能会以意想不到的方式发生变化。据推测,它会在发生时伴随适当的警告,但为什么要用一个无意义的属性标记您的代码,这些属性以后可能有意义,从而迫使您更改代码?此时,scope
应该只用在委托上,所以 in
应该只用在委托上,通常不需要 const
委托。所以,不要使用 in
.
所以,最终,您真正要问的是您应该使用 const
还是 const ref
。
简短的回答是你通常不应该使用 ref
除非你想改变你传递的参数。我还要指出这个问题对除了结构之外的任何东西都没有意义,也许静态数组,因为 类 已经是引用类型,而内置类型的 none(静态数组除外)的复制成本很高。较长的答案是...
移动语义内置于 D 中,因此如果您有一个按值获取其参数的函数 - 例如
auto foo(Bar bar) { ... }
如果可以的话,它会移动参数。如果你给它传递一个左值(一个可以在赋值左侧的值),那么这个值将被复制,除非在编译器能够确定它可以优化复制的情况下(例如,在该函数调用之后从未使用该变量时),但这将取决于所使用的编译器和编译器标志。因此,按值将变量传递给函数通常会产生一个副本。但是,如果您向函数传递一个右值(不能放在赋值左侧的值),那么它将移动该对象而不是复制它。这与 C++ 不同,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
always 需要一个左值(即使 const
)。所以,如果你开始把 ref const(T)
放在任何地方,就像你在 C++ 中做 const T&
一样,你将会有很多调用起来很烦人的函数,因为每个临时函数都必须分配到一个变量首先调用函数。因此,您应该认真考虑只在想要改变传入的变量而不是为了提高效率时才使用 ref
。当然,你的默认设置应该是不通过 const ref
,但如果你确实需要额外的效率,你有两个选择:
- 在
ref
-ness 上重载函数,这样你就有一个由 const ref
接受的重载和一个由 ref
接受的重载,这样左值就可以传递给一个而不被被复制,并且右值被传递给另一个而不需要一个无关的变量。例如
auto foo(const Bar bar) { foo(bar); }
auto foo(ref const(Bar) bar) { ... }
这有点烦人,但当您只有一个参数 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) { ... }
所以,这在一定程度上是有效的,但并不是特别令人愉快。如果你像我在这里做的那样定义重载,那么它也有一个缺点,即右值最终被传递给一个包装函数(添加一个额外的函数调用——尽管一个应该是完全可内联的),这意味着它们是现在由 ref
传递给主重载,如果这些参数之一被传递给另一个函数或 returned,编译器无法进行移动,而如果 ref
没有被牵扯进来,那就可以了。这就是现在争论不应该像在 C++98 中那样在 C++11 中大量使用 const T&
的原因之一。
您可以通过为每个重载复制函数体来解决该问题,但这显然会造成维护问题以及代码膨胀。
- 另一种方法是使用
auto ref
,它基本上可以为您做到这一点,但必须对函数进行模板化。例如
auto foo()(const auto ref Bar bar, const auto ref Glop glop) { ... }
因此,现在您只有一个重载,但每次使用 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++ 的 [=23] =] 会起作用的。
因此,在大多数情况下,当您希望函数采用 T
时,您应该默认采用普通 T
。然后,如果您 知道 效率是一个问题,您可以考虑使用某种形式的 ref
(如果您可能喜欢 auto ref
或 const auto ref
不处理虚函数)。但默认不使用 ref
。这样你的生活会更愉快。
void foo(T, size_t size)(in T[size] data){...}
//vs
void foo(T, size_t size)(const ref T[size] data){...}
根据 看来,在 C++ 中 pass by value
在某些情况下可能更快。
但是D有一个特殊的关键字in
,我不知道什么时候应该使用它。 in
总是生成副本还是编译器优化?
是否有任何准则可以帮助我在 const ref
和 in
之间做出选择?
我认为你不应该在函数参数上使用 in
。 in
是 D1 中的一个工件,它被保留以减少代码损坏,但被更改为等同于 const scope
。因此,每当您想到在函数参数上键入 in
时,请想到 const scope
,因为这才是您真正要做的。 scope
目前只对委托做任何事情,在这种情况下,它告诉编译器接受委托的函数不会 return 它或将它分配给任何东西,因此不必关闭分配以保存该委托的状态(因此,它在许多情况下提高了委托的效率),而对于所有其他类型,它被完全忽略,这意味着使用它是没有意义的(并且可能会造成混淆),如果它曾经 does 对其他类型有其他含义(例如,有人建议它应该强制作为 scope
传入的指针不能转义函数),那么语义您的代码可能会以意想不到的方式发生变化。据推测,它会在发生时伴随适当的警告,但为什么要用一个无意义的属性标记您的代码,这些属性以后可能有意义,从而迫使您更改代码?此时,scope
应该只用在委托上,所以 in
应该只用在委托上,通常不需要 const
委托。所以,不要使用 in
.
所以,最终,您真正要问的是您应该使用 const
还是 const ref
。
简短的回答是你通常不应该使用 ref
除非你想改变你传递的参数。我还要指出这个问题对除了结构之外的任何东西都没有意义,也许静态数组,因为 类 已经是引用类型,而内置类型的 none(静态数组除外)的复制成本很高。较长的答案是...
移动语义内置于 D 中,因此如果您有一个按值获取其参数的函数 - 例如
auto foo(Bar bar) { ... }
如果可以的话,它会移动参数。如果你给它传递一个左值(一个可以在赋值左侧的值),那么这个值将被复制,除非在编译器能够确定它可以优化复制的情况下(例如,在该函数调用之后从未使用该变量时),但这将取决于所使用的编译器和编译器标志。因此,按值将变量传递给函数通常会产生一个副本。但是,如果您向函数传递一个右值(不能放在赋值左侧的值),那么它将移动该对象而不是复制它。这与 C++ 不同,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
always 需要一个左值(即使 const
)。所以,如果你开始把 ref const(T)
放在任何地方,就像你在 C++ 中做 const T&
一样,你将会有很多调用起来很烦人的函数,因为每个临时函数都必须分配到一个变量首先调用函数。因此,您应该认真考虑只在想要改变传入的变量而不是为了提高效率时才使用 ref
。当然,你的默认设置应该是不通过 const ref
,但如果你确实需要额外的效率,你有两个选择:
- 在
ref
-ness 上重载函数,这样你就有一个由const ref
接受的重载和一个由ref
接受的重载,这样左值就可以传递给一个而不被被复制,并且右值被传递给另一个而不需要一个无关的变量。例如
auto foo(const Bar bar) { foo(bar); }
auto foo(ref const(Bar) bar) { ... }
这有点烦人,但当您只有一个参数 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) { ... }
所以,这在一定程度上是有效的,但并不是特别令人愉快。如果你像我在这里做的那样定义重载,那么它也有一个缺点,即右值最终被传递给一个包装函数(添加一个额外的函数调用——尽管一个应该是完全可内联的),这意味着它们是现在由 ref
传递给主重载,如果这些参数之一被传递给另一个函数或 returned,编译器无法进行移动,而如果 ref
没有被牵扯进来,那就可以了。这就是现在争论不应该像在 C++98 中那样在 C++11 中大量使用 const T&
的原因之一。
您可以通过为每个重载复制函数体来解决该问题,但这显然会造成维护问题以及代码膨胀。
- 另一种方法是使用
auto ref
,它基本上可以为您做到这一点,但必须对函数进行模板化。例如
auto foo()(const auto ref Bar bar, const auto ref Glop glop) { ... }
因此,现在您只有一个重载,但每次使用 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++ 的 [=23] =] 会起作用的。
因此,在大多数情况下,当您希望函数采用 T
时,您应该默认采用普通 T
。然后,如果您 知道 效率是一个问题,您可以考虑使用某种形式的 ref
(如果您可能喜欢 auto ref
或 const auto ref
不处理虚函数)。但默认不使用 ref
。这样你的生活会更愉快。