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

时间:2018-02-27 01:47:57

标签: c++ oop functional-programming c++14 encapsulation

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

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

面向对象的样式

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风格

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样式

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"看起来很时髦。

功能样式

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的功能和多态性的继承

3 个答案:

答案 0 :(得分: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。

答案 1 :(得分: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 :(得分:2)

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

所谓的面向对象选项

它不是面向对象的,它只是将东西粘在物体上。用b和c&#34;做某事的功能。不应该是包含它们的对象的成员;该函数不是a,a b和c的组合所固有的。

Functor选项

与所谓的面向对象选项相似的批评。只是在没有充分理由的情况下在那里贴上一个仿函数。

功能选项

你真的只是使用lambda而不是构造结构......并不可怕,但请看下一个选项:

所谓的C风格选项 - 胜利

这在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将参数绑定到本地名称。另外,请注意我在引用语义上使用了更多的值语义,让编译器发挥其魔力(它可能会发生;即使它对于少数int来说并不重要)。

这是我推荐的。