gpt4 book ai didi

c++ - 通过右值引用而不是值获取接收器参数以强制接口(interface)的性能使用

转载 作者:行者123 更新时间:2023-12-02 10:32:58 27 4
gpt4 key购买 nike

在一次代码审查中,我和我的同事正在讨论我正在编写的函数的接口(interface)。我们的代码库使用 C++17,我们不使用异常(视频游戏)。

我声称采用接收器参数的惯用 C++ 方法将具有高性能,同时还能保持接口(interface)的灵 active ,允许调用者根据需要传递拷贝或从拥有的值移动。通过惯用的方式,我的意思是要么一个函数按值获取参数,要么为 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 左值引用重载),并且如果调用者希望传递一个拷贝,他们必须制作一个本地拷贝并将其移动到函数中。

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.
}

我从来没有想过这样的界面。我的一个反驳论点是,按值(value)获取可以更好地表达意图,即带有签名

void f(T x);

调用者知道 f 已经取得了 x 的所有权。与

void g(T&& x);

g 可能有所有权,也可能没有,这取决于 f 的实现。

有没有最好的方法?我是否以某种方式遗漏了一些论点?

最佳答案

你基本上有那些构造函数选项:

class myclass {
public:
// #1 myclass(const std::string& s) : s(s) {}
// #2 myclass(std::string&& s) : s(std::move(s)) {}

// #3 myclass(std::string s) : s(std::move(s)) {}

// #4 template <typename T> myclass(T&& t) : s(std::forward<T>(t)) {}

std::string s;
};

#3 不能与 #1#2 一起出现 -> 模棱两可的调用

并调用

std::string s;
myclass A(s);
myclass B(std::string(s));
myclass C(std::move(s));
myclass D("temporary");
myclass E({5, '*'});

以下是复制/移动构造函数的计数。

                        | A | B | C | D | E |
------------------------|---|---|---|---|---|
1 Copy |<1>| 2 | 1 | 1 | 1 | <.> Denotes best result (by column)
const l-value ref Move |<0>| 1 | 1 | 0 | 0 |
Other |<0>| 0 | 0 | 1 | 1 |
------------------------|---|-v-|-v-|-v-|-v-| B/C/D/E would prefer overload 2
2 Copy | X |<1>|<0>| 0 |<0>|
r-value ref Move | X |<1>|<1>| 1 |<1>| X denotes invalid case
Other | X |<0>|<0>| 1 |<1>|
------------------------|---|---|---|---|---|
3 Copy | 1 | 1 | 0 | 0 |<0>|
by value Move | 1 | 2 | 2 | 1 |<1>|
Other | 0 | 0 | 0 | 1 |<1>|
------------------------|---|---|---|---|---|
4 Copy |<1>|<1>|<0>|<0>| X |
Forwarding ref Move |<0>|<1>|<1>|<0>| X |
Other |<0>|<0>|<0>|<1>| X |
--------------------------------------------/

可能的配置:

  • #1 only:处理所有情况,但临时复制
  • #1/#2:(B/C/D/E 将使用 #2),因此除了就地构造之外的最佳结果
  • #3 only:处理所有情况,但做额外的移动
  • #4 only:处理大多数常规情况,最好的结果
  • #1/#2/#4:最佳结果(注意 #4)与非常量左值完全匹配)
  • #2/#4:最佳结果
  • #2:禁止复制,但显式复制 (B) 比 #1/#2 多走 1 步

如你所见:

  • 转发引用(#4)有最好的结果。
  • 通过 const ref (#1) 具有最佳的复制性能,但其他性能较差。
  • Then By Value (#3) 是第二个“最差”,但只比最好的多一步。

其他要点:

  • 在 C++11 之前只有 #1 可用(大多数界面中的默认设置也是如此)
  • 只有 #1 可能意味着没有所有权转让。
  • 仅移动 (#2) 禁止隐式复制
  • 按值#3 的目的是只写一个重载作为妥协。

现在比较#1/#2#2#3:

  • 对于仅移动类型:

  • #1/#2 无关

  • #3 立即下沉。

  • #2 有机会处理异常保证:如果它抛出,则不需要消耗对象。

除非您想要保证和/或立即下沉,否则我会使用右值传递 (#2)。

对于现有的代码库,我会保持一致性。

对于可复制类型:

  • #1/#2 是最有效的,但允许不需要的复制。
  • #3 很方便(而且只有一个额外的移动),但允许不需要的复制,保证沉没。
  • #2 避免不需要的复制。

现在主要是您要保证和允许的内容:

  • 最佳性能 -> #1/#2
  • 无(隐式)拷贝 -> #2
  • 立即/保证接收器 -> #3
  • 与仅可移动类型的一致性

对于现有的代码库,我会保持一致性。

关于c++ - 通过右值引用而不是值获取接收器参数以强制接口(interface)的性能使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61597860/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com