C++实现命名参数惯用法(非常详细)
C++ 只支持位置参数,这意味着传递给函数的参数是基于参数位置的。其他语言还支持命名参数——在调用时指定参数名称。这在参数有默认值时特别有用。函数可能有带默认值的参数,尽管它们通常出现在非默认参数的后面。
然而,如果你只想提供值给部分默认参数,除非给函数参数列表中位于这些部分默认参数前的参数都提供值,否则无法这么做。
命名参数惯用法技术提供了模拟命名参数的方法来解决这个问题。为了展示命名参数惯用法,我们将使用以下代码片段中的 control 类:
1) 创建封装函数参数的类:
2) 将需要访问这些属性的类或函数声明为 friend,以避免写 getter:
3) 原函数中没有默认值的所有位置参数,都应该变成类构造函数里的没有默认值的位置参数:
4) 对于原函数中有默认值的所有位置参数,应该有函数(同样的名字)内部设置其值并返回类的引用:
5) 原函数需要被修改或提供重载,来接受新类的参数,以从此类中可读取属性值:
如果把这些全部整合在一起,结果如下所示:
创建最初实现的 control 对象如下所示:
尽管实现此惯用法不只有单一策略,本节的示例是很典型的。control 类属性,作为构造函数的参数,被放到独立的 control_properties 类,类 control 作为友元类允许访问 control_properties 的私有数据成员而不需要提供 getter。副作用是限制了 control_properties 在 control 类之外的使用。
control 类构造函数的必要参数也是 control_properties 构造函数的必要参数。对于所有其他有默认值的参数,control_properties 类定义了具有相关名称的函数来将数据成员设置为提供的参数,并返回 control_properties 的指针。这使用户能够以任意顺序链接对这些函数的调用。
control 类的构造函数被新的构造函数替代,新构造函数具有对 control_properties 对象的常量引用的单一参数,其数据成员被复制到 control 对象的数据成员。
以这种用命名参数惯用法方式来创建 control 对象的代码片段如下:
然而,如果你只想提供值给部分默认参数,除非给函数参数列表中位于这些部分默认参数前的参数都提供值,否则无法这么做。
命名参数惯用法技术提供了模拟命名参数的方法来解决这个问题。为了展示命名参数惯用法,我们将使用以下代码片段中的 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));