首页 > 编程笔记 > C++笔记 阅读:1

C++实现命名参数惯用法(非常详细)

C++ 只支持位置参数,这意味着传递给函数的参数是基于参数位置的。其他语言还支持命名参数——在调用时指定参数名称。这在参数有默认值时特别有用。函数可能有带默认值的参数,尽管它们通常出现在非默认参数的后面。

然而,如果你只想提供值给部分默认参数,除非给函数参数列表中位于这些部分默认参数前的参数都提供值,否则无法这么做。

命名参数惯用法技术提供了模拟命名参数的方法来解决这个问题。为了展示命名参数惯用法,我们将使用以下代码片段中的 control 类:
class control
{
    int id_;
    std::string text_;
    int width_;
    int height_;
    bool visible_;

public:
    control(
        int const id,
        std::string_view text = "",
        int const width = 0,
        int const height = 0,
        bool const visible = false):
        id_(id), text_(text),
        width_(width), height_(height),
        visible_(visible)
    {}
};
control 类表示虚拟控制台,如按钮或输入框,有诸如数字标识、文本、大小和可见性等属性。这些都提供给构造函数,除了 ID,其他都有默认值。实际上,这样的类会有更多属性,如文本刷、背景刷、边框样式、字体大小、字体类型等。

C++命名参数惯用法的实现过程

为了实现函数(通常带有很多默认参数)的命名参数惯用法,如下做:
1) 创建封装函数参数的类:
class control_properties
{
    int id_;
    std::string text_;
    int width_ = 0;
    int height_ = 0;
    bool visible_ = false;
};

2) 将需要访问这些属性的类或函数声明为 friend,以避免写 getter:
friend class control;

3) 原函数中没有默认值的所有位置参数,都应该变成类构造函数里的没有默认值的位置参数:
public:
    control_properties(int const id) :id_(id)
    {}

4) 对于原函数中有默认值的所有位置参数,应该有函数(同样的名字)内部设置其值并返回类的引用:
public:
    control_properties& text(std::string_view t)
    { text_ = t.data(); return *this; }

    control_properties& width(int const w)
    { width_ = w; return *this; }

    control_properties& height(int const h)
    { height_ = h; return *this; }

    control_properties& visible(bool const v)
    { visible_ = v; return *this; }

5) 原函数需要被修改或提供重载,来接受新类的参数,以从此类中可读取属性值:
control(control_properties const & cp):
    id_(cp.id_),
    text_(cp.text_),
    width_(cp.width_),
    height_(cp.height_),
    visible_(cp.visible_)
{}

如果把这些全部整合在一起,结果如下所示:
class control;
class control_properties
{
    int id_;
    std::string text_;
    int width_ = 0;
    int height_ = 0;
    bool visible_ = false;

    friend class control;
public:
    control_properties(int const id) :id_(id)
    {}

    control_properties& text(std::string_view t)
    { text_ = t.data(); return *this; }

    control_properties& width(int const w)
    { width_ = w; return *this; }

    control_properties& height(int const h)
    { height_ = h; return *this; }

    control_properties& visible(bool const v)
    { visible_ = v; return *this; }
};

class control
{
    int id_;
    std::string text_;
    int width_;
    int height_;
    bool visible_;

public:
    control(control_properties const & cp):
        id_(cp.id_),
        text_(cp.text_),
        width_(cp.width_),
        height_(cp.height_),
        visible_(cp.visible_)
    {}
};

深度剖析命名参数惯用法

最初 control 类的构造函数有很多参数。在真实世界代码中,你能找到有更多参数的示例。实际上,可能的解决方案是,将通用布尔类型属性以比特标志组合,然后则可以以单个整型参数传递(如 control 的边框样式,它定义边框可见位置:上、下、左、右或任意这四个的组合)。

创建最初实现的 control 对象如下所示:
control c(1044, "sample", 100, 20, true);
命名参数惯用法允许你使用名称,以任意顺序只指定想要的参数值,这比固定位置顺序更直观。

尽管实现此惯用法不只有单一策略,本节的示例是很典型的。control 类属性,作为构造函数的参数,被放到独立的 control_properties 类,类 control 作为友元类允许访问 control_properties 的私有数据成员而不需要提供 getter。副作用是限制了 control_properties 在 control 类之外的使用。

control 类构造函数的必要参数也是 control_properties 构造函数的必要参数。对于所有其他有默认值的参数,control_properties 类定义了具有相关名称的函数来将数据成员设置为提供的参数,并返回 control_properties 的指针。这使用户能够以任意顺序链接对这些函数的调用。

control 类的构造函数被新的构造函数替代,新构造函数具有对 control_properties 对象的常量引用的单一参数,其数据成员被复制到 control 对象的数据成员。

以这种用命名参数惯用法方式来创建 control 对象的代码片段如下:
control c(control_properties(1044)
    .visible(true)
    .height(20)
    .width(100));

相关文章