在C ++中封装大量参数14

问题描述 投票:6回答:3

我想编写一个使用许多参数的函数,我将其称为abc。我有四种在C ++ 14中实现它的选择。

对于2018年的一个新的现代C ++项目,哪种风格最符合ISO C++的哲学?其他风格指南推荐哪些款式?

Object oriented style

class Computer {
    int a, b, c;
public:
    Computer(int a, int b, int c) : a(a), b(b), c(c) {}
    int compute(int) const {
        // do something with a, b, c
    }
};
...
const Computer computer(a, b, c);
int result = computer.compute(123);

优点:

  • 易于C ++程序员掌握

缺点:

  • 要计算地图或折叠操作中的东西,我们必须做笨重的[computer](int input){ return computer.compute(input); }

C style

struct ComputeParams {
    int a, b, c;
};

int compute(const ComputeParams &params, int input) {
    // do something with params.a, params.b, params.c
}
...
const ComputeParams params{a, b, c};
int result = compute(params, 123);

优点:

  • 易于C程序员掌握

缺点:

  • compute的详细实现涉及调用params.a而不是a
  • 详细调用,每次都必须传入一个结构。

Functor style

struct Computor {
    int a, b, c;
    int operator()(int input) const {
        // do something with a, b, c
    }
};
...
const Computor compute{a, b, c};
int result = compute(123);

优点:

  • 面向对象风格的所有优点,加上它看起来像一个函数
  • 可用于map,fold和for_each等功能操作

缺点:

  • “functor”这个词看起来很时髦。

Functional style

auto genCompute(int a, int b, int c) {
    return [a, b, c](int input) -> int {
        // do something with a, b, c
    }
}
...
auto compute = genCompute(a, b, c);
int result = compute(123);

优点:

  • 易于OCaml程序员掌握
  • 可用于map,fold和for_each等功能操作
  • 技术上与仿函数相同

缺点:

  • 很难让C ++和C程序员掌握
  • 由于lambda函数是由编译器生成的唯一类型,因此可能需要使用auto或模板魔法来内联lambda函数,或者std::function具有性能开销
  • 无法接受vtable的功能和多态性的继承
c++ oop functional-programming c++14 encapsulation
3个回答
4
投票

还有更多的说法支持功能样式:您可以轻松地为某些参数值提供特殊/优化版本

std::function<int(int)> getCompute(int a, int b, int c)
{
    if(a==0)
        return [b,c](int input) { /* version for a=0 */ };
    if(b==0)
        return [a,c](int input) { /* version for b=0 */ };
    if(c==0)
        return [a,b](int input) { /* version for c=0 */ };
    /* more optimized versions */
    return [a,b,c](int input) { /* general version */ };
}

与其他选项相比,等价的东西并不简单。不幸的是,这需要使用std::function来包裹不同的lambda。


2
投票

很多这是基于意见的,但我会戴上帽子。

面向对象的风格

不是你的粉丝。由于您支持的唯一操作是compute,因此它的名称实际上是operator ()operator ()意味着你可以很好地使用algorithm标题,所以这是Functor和Functional风格的低级解决方案。

此外,您可能会使用此解决方案执行更差的代码。如果你很好奇,那么整个谈话都值得关注,但Chandler Carruth(LLVM / Clang开发人员)explains how the compiler sees your code(跳到大约1:32:37,但整个演讲很棒)。它的要点是你在这个实现中有一个隐式指针,指针/引用对于编译器来说更难以优化。

C风格

不仅仅是为了API的粉丝。你在你的缺点中提到调用需要传递struct,这在处理需要单个操作的库时(例如,algorithm中的所有内容)是一个问题。你可以使用一个捕捉你的struct的lambda绕过这个,但那时我不知道你获得了什么。

功能风格

这就是我要去的方式以及我正在推动的工作。我见过的例子表明,调用lambda函数并不比直接调用函数慢,因为编译器可以积极地内联(他们知道确切的类型)。如果C ++程序员为这种风格而烦恼,因为它是不同的/新的,告诉他们加快速度,因为他们有几个标准:)。

至于最佳实践以及社区正在使用的内容,Cppcon的示例似乎更倾向于仿函数/功能风格。 C ++作为一种语言看起来像是一般的功能设计。


2
投票

tl; dr:使用下面的代码片段。

The supposedly-Object-oriented option

它不是面向对象的,只是将东西放在一个对象中。 “用b和c做某事”的功能不应该是包含它们的对象的成员;该功能不是a,b和c组合所固有的。

The Functor option

类似的批评与所谓的面向对象的选择。只是在没有充分理由的情况下将仿函数放在那里。

The functional option

你真的只是使用lambda而不是构造结构......不是很糟糕但是看下一个选项:

The supposedly-C-style option - for the win

这在C ++中非常流行。结构是一个非常好的对象 - 具有公共数据成员和默认构造函数和析构函数。

此外,您的利弊都可以轻松解决,代码更简单,更简单:

struct ComputeParams {
    int a, b, c;
};

auto compute(ComputeParams params, int input) {
    auto [a, b, c] = params;
    // do something with a, b and c
}
auto result = compute(ComputeParams{a, b, c}, 123);

这是有效的C ++ 17(使用结构化绑定);在C ++ 14中,您需要使用std::tie将参数绑定到本地名称。另外,请注意我在引用语义上使用了更多的值语义,让编译器发挥其魔力(它可能会发生;即使它对于一些ints并不重要)。

这是我推荐的。

© www.soinside.com 2019 - 2024. All rights reserved.