首页 > 解决方案 > C ++ - 将带有引用的长参数列表重构为结构

问题描述

我喜欢在调用它们的构造函数后简单地拥有具有有效状态的类 - 即所有必需的依赖项都传递到构造函数中。

我也喜欢将所需的依赖项作为引用传递,因为在编译时简单地禁止 nullptr 作为这些参数的值。

例子:

class B;

class A
{
public:
    A(B& b) : b(b) {}

private:
    B& b;
}

在实例化 A 之后,您(几乎)可以保证该实例处于有效状态。我发现这种代码风格可以避免编程错误。

我的问题与当它们有很多依赖关系时重构这些类有关。

例子:

// Includes for B, C, D, E, F...

class A
{
public:
    A(B b, C c, D d, E e, F f) : b(b), c(c), d(d), e(e), f(f) {}

private:
    B b;
    C c;
    D d;
    E e;
    F f;
}

通常,我将一长串参数放在结构中,如下所示:

struct Deps
{
    B b;
    C c;
    D d;
    E e;
    F f;
}

class A
{
public:
    A(Deps deps) : b(deps.b), c(deps.c), d(deps.d), e(deps.e), f(deps.f) {}

private:
    B b;
    C c;
    D d;
    E e;
    F f;
}

这样,它使调用站点更加明确并且更不容易出错:由于必须命名所有参数,因此您不会因为将它们以错误的顺序错误地切换它们而面临风险。

可悲的是,该技术在参考文献中效果不佳。在 Deps 结构中有引用会将问题转发给该结构:然后,Deps 结构需要有一个初始化引用的构造函数,然后该构造函数将有一个很长的参数列表,基本上什么也解决不了。

现在的问题是:有没有办法在包含引用的构造函数中重构长参数列表,这样没有函数会导致参数列表很长,所有参数始终有效,并且类的任何实例都不会处于无效状态(即某些依赖项未初始化或为空)?

标签: c++

解决方案


实际上,有一个非常优雅/简单的解决方案,使用std::tuple

#include <tuple>

struct A{};
struct B{};
struct C{};
struct D{};
struct E{};
struct F{};

class Bar
{
public:
    template<class TTuple>
    Bar(TTuple refs)
        : a(std::get<A&>(refs))
        , b(std::get<B&>(refs))
        , c(std::get<C&>(refs))
        , d(std::get<D&>(refs))
        , e(std::get<E&>(refs))
        , f(std::get<F&>(refs))
    {
    }

private:
    A& a;
    B& b;
    C& c;
    D& d;
    E& e;
    F& f;
};


void test()
{
    A a; B b; C c; D d; E e; F f;
    // Different ways to incrementally build the reference holder:
    auto tac = std::tie(a, c); // This is a std::tuple<A&, C&>.
    auto tabc = std::tuple_cat(tac, std::tie(b));
    auto tabcdef = std::tuple_cat(tabc, std::tie(d, f), std::tie(e));

    // We have everything, let's build the object:
    Bar bar(tabcdef);
}

https://godbolt.org/z/pG1R7U

std::tie精确地存在以创建引用元组。我们可以使用std::tuple_cat. 并std::get<T>允许从给定的元组中准确检索我们需要的引用。

这有:

  • 最小样板:您只需std::get<X&>在每个引用类型的成员初始化器列表中写入。无需提供/重复任何其他内容即可将其用于更多引用或包含引用的类型。

  • 完全编译时安全:如果您忘记提供引用或提供两次,编译器会报错。类型系统对所有必要的信息进行编码。

  • 添加引用的顺序没有限制。

  • 没有手写模板机械。使用标准工具而不是手写模板机制意味着您不会引入错误/忘记极端情况。这也意味着这种方法的用户/读者没有任何需要涉水的东西(并且可能会逃跑,尖叫)。

我认为这是一个非常简单的解决方案,只是因为std::tuple和朋友们已经实现了这里需要的所有元编程。它仍然比“一个长列表中的所有内容”稍微复杂一些,但我很确定这将是值得的权衡。

(我以前的手写模板版本存在于编辑历史中。但我意识到这std::tuple已经完成了我们需要的一切。)


推荐阅读