【发布时间】:2020-08-19 05:04:54
【问题描述】:
在一次代码审查中,我和我的同事正在讨论我正在编写的函数的接口。我们的代码库使用 C++17,我们不使用异常(视频游戏)。
我声称采用 Sink 参数的惯用 C++ 方式将是高性能的,同时还保持接口灵活,允许调用者传递副本或根据需要从拥有的值移动。通过惯用的方式,我的意思是一个函数按值获取参数,或者为 const 左值和右值引用设置了一个过载集(在左值情况下需要少移动一次,代价是一些代码重复)。
struct A {};
class ByValue
{
public:
ByValue(std::vector<A> v)
: m_v(std::move(v))
{}
private:
std::vector<A> m_v;
};
class RefOverloads
{
public:
RefOverloads(std::vector<A> const& v)
: m_v(v)
{}
RefOverloads(std::vector<A>&& v)
: m_v(std::move(v))
{}
private:
std::vector<A> m_v;
};
int main()
{
std::vector<A> v0;
ByValue value0(v0);
ByValue value1(std::move(v0));
std::vector<A> v1;
RefOverloads ref0(v1);
RefOverloads ref1(std::move(v1));
}
另一方面,我的同事不喜欢暗中制作昂贵的副本很容易。他希望这些接收器参数始终通过右值引用(没有 const 左值 ref 重载),并且如果调用者希望传递一个副本,他们必须制作一个本地副本并将其移动到函数中。
class RvalueRefOnly
{
public:
RvalueRefOnly(std::vector<A>&& v)
: m_v(std::move(v))
{}
private:
std::vector<A> m_v;
};
int main()
{
std::vector<A> v;
//RvalueRefOnly failedCopy(v); // Fails purposefully.
std::vector<A> vCopy = v; // Explicit copy of v.
RvalueRefOnly okCopy(std::move(vCopy)); // Move into okCopy.
}
我从来没有想过这样的界面。我的一个反驳观点是,按价值获取更好地表达意图,即带有签名
void f(T x);
调用者知道f 已获得x 的所有权。与
void g(T&& x);
g 可能有所有权,也可能没有,这取决于f 的实现。
有没有最好的方法?我是否以某种方式遗漏了一些论点?
【问题讨论】:
-
在大多数情况下,按值传递将由您的编译器完美优化,并且值语义对于屏幕编写代码的人来说更容易推理。如果可能,我会通过价值传递,除非您的分析器向您显示不这样做的真正理由。除非您有证明需要这样做,否则不要让它变得复杂。
-
您的最终目标是什么,以获得最佳性能?
-
这完全是一个 API 设计问题,可能只是见仁见智。与移动语义一起使用时,按值传递可能会同样快。问题是您是否要向用户强加他们制作自己的副本并在他们打算时移动它的要求,以避免在不打算时意外复制。您可以争辩说在副本会造成破坏的情况下更安全,并且您可以争辩说如果这不是常见的情况是不方便的。
-
@JesperJuhl 在大多数情况下,您的编译器会完美地优化传递值 真的吗?我知道按值返回是,但按值传递 AFAIK 在大多数情况下会留下很多性能。
-
另一种说法:你的同事的论点是 “我的同事不喜欢很容易隐式地制作昂贵的副本。” 你的同事是对的 如果 这种担忧是个问题。这取决于您的项目或产品的具体独特情况。在我看来,这个问题没有客观的答案。
标签: c++