C++ 结构作为函数参数与多个常量引用参数与 C++ 核心指南与性能?

C++ struct as function argument vs. multiple const ref parameters vs. C++ core guidelines vs. performance?

提问人:Martin Ba 提问时间:2/20/2023 最后编辑:Martin Ba 更新时间:2/21/2023 访问量:130

问:

我目前正在尝试决定是否“构造”一个相当长的参数集:

void fooCopy1(std::string const& source, std::string const& destination, std::string const& filter, std::string const& temp);

对此:

struct FooCopyArgs {
    std::string source;
    std::string destination;
    std::string filter;
    std::string temp;
};
void fooCopy2(FooCopyArgs const& args);

正如在另外两个问题中已经回答的那样:

重构它可能具有几个可读性/可维护性优势。有关其中的一堆,请参阅链接的问题。

然而,总的来说,我在C++中看到了这种方法的一个“大”问题,那就是在调用此函数之前始终必须复制字符串,而不是使用参数。const&

这将违反C++核心准则F.16“通过值传递廉价复制的类型,通过引用const传递其他类型”。

也就是说,通常由 传递的非廉价只读参数需要复制到结构中,这将是普遍的悲观情绪。const ref

(是的,结构本身将由 const ref 传递,但结构数据成员需要先复制。

例:

const string temp = ...;
const string filter = ...;
...
fooCopy2({"sourceItem", "targetItem", filter, temp});

对于 ,这是本地定义的参数值,这无关紧要。 但是,对于传递下来的参数,我们将有一个无关的副本,可以通过简单的方法避免。"sourceItem"filtertempconst&

免责声明:显然,在 99% 的情况下,性能影响甚至在最终应用程序中都无法观察到,但它仍然留下了不好的味道,尤其是在 F.16 等“基本”规则的上下文中。

问题:有没有聪明的方法可以解决这个问题,即:

  • 有一个安全的结构作为参数类型(成员不安全;极易出现悬空引用)const&
  • 避免非廉价类型的无关副本
  • 如果 severeal 函数使用此模式,则保持可组合性

附录:为什么使用成员不安全:const&

struct HasConstRef {
    std::string const& member;
};

void f(HasConstRef const& arg) {
    std::cout << arg.member << "\n";
}

HasConstRef arg_producer() {
    HasConstRef result = { "there be dragons" };
    return result; // UB
}

void f_call() {
    f(arg_producer()); // *boom*, no diagnostic required
}

虽然我完全同意当前的答案,即可以正确使用 const-ref-membered 结构,但在没有任何编译器帮助的情况下,它也很容易被错误地使用。我宁愿不这样做。

我发现“难以正确使用”与“不可能错误使用”相去甚远。而“正常”的 const-ref 参数,就像普通数据成员一样,很难错误地使用(就 C++ 而言)。 另一方面,易于错误地使用。其他人似乎不同意:请参阅下面的答案。仍然欢迎替代方案。const& members

C++ 参数传递 pass-by-const-reference

评论

2赞 Aconcagua 2/20/2023
为什么要复制?字符串也可以作为引用或指针存储在结构中。相反,引用无论如何都不是可选的。可能需要一个合适的构造函数。
1赞 NathanOliver 2/20/2023
如果您只将其中一个对象传递到调用堆栈中(到函数中),那么拥有引用成员是安全的
0赞 Martin Ba 2/20/2023
@NathanOliver - 是的,我知道它可能是安全的,但是在没有任何编译器帮助的情况下,结构和整个东西将非常脆弱且容易出错。
0赞 lorro 2/20/2023
@MartinBa 是什么让您认为它“非常脆弱且容易出错”?在类中清楚地记录了这些是引用/非空 ptrs。
1赞 lorro 2/20/2023
@MartinBa 然后只需添加一个 ctor 或使用指针即可。(顺便说一句,当你退出时不会消失,它至少会一直存在到静态破坏,但我能猜到你在想什么 - 即便如此,这也是一个问题)。HasConstRef"there be dragons"arg_producer()arg_producer

答:

5赞 KamilCuk 2/20/2023 #1

那你为什么要删除?以下编译很好:const&

#include <string>
using string = std::string;
struct FooCopyArgs {
    std::string const& source;
    std::string const& destination;
    std::string const& filter;
    std::string const& temp;
};
void fooCopy2(FooCopyArgs const& args);
int main() {
    const string temp = "";
    const string filter = "";
    fooCopy2({"sourceItem", "targetItem", filter, temp});
}

评论

1赞 NathanOliver 2/20/2023
当我指出这一点时,OP 的回应是:是的,我知道它可能是安全的,但是没有任何编译器帮助,结构和整个东西将非常脆弱且容易出错
0赞 Martin Ba 2/20/2023
一旦有人将您的版本作为局部变量放在堆栈上,它就会崩溃。没有任何编译器警告。FooCopyArgsWithConstRef
0赞 lorro 2/20/2023
这是解决方案;但是,由于引用不会使其成为 AggregateType,因此有时最好使用指针(可能带有注释)。如果需要,这样可以很容易地编写,从而使它变得更好。(或者,您可以添加 ctor。non_null<std::string*>{&source, &destination, &filter, &temp}
1赞 KamilCuk 2/20/2023
FooCopyArgsWithConstRef version on the stack as a local variable我不明白。你能举个例子吗?如果把一个局部变量放在main中,就没有区别了,反正创建一个临时的,它是一样的,只是变量的寿命会略有不同。FooCopyArgs {...}
0赞 lorro 2/20/2023
@MartinBa 你认为它为什么会崩溃?只要原始参数有效,它就有效。你唯一没有的就是延长一个临时的待命点(读作:结构构造)点的寿命,但是,如果你这样做,你需要注意把它放在一个变量中。
2赞 Dima 2/20/2023 #2

在这种情况下,我不会担心性能,而会更多地担心语义。如果项目确实属于一起,则应将它们组合成一个结构或类。换句话说,你希望你的类和结构具有高度的内聚力。

也许并且应该是类的成员,并且应该是同一类中的成员函数。据推测,并且不经常更改,而 和 做。filtertempfooCopy()filtertempsourcedestination

顺便说一句,考虑重命名为更具描述性的名称。temp