19.4 命名参数模式(Named Parameters)
相关章节
在本节中你将学到…
- 命名参数模式的设计动机和优势
- CGAL parameters的实现原理
- 如何编写支持命名参数的函数
- 命名参数与默认参数的区别
- 实际应用中的最佳实践
19.4.1 概念解释
什么是命名参数模式?
命名参数模式(Named Parameters Pattern)是一种编程技术,允许函数调用者通过参数名而非位置来指定参数。这在参数众多、大部分有默认值的情况下特别有用。
类比理解: 想象你在一家高级餐厅点餐。传统方式(位置参数)是:“我要第3道菜、加辣、不要洋葱、用橄榄油”。而命名参数方式是:“我要宫保鸡丁,辣度:微辣,配料:去掉洋葱,用油:橄榄油”。后者更清晰,也不容易出错。
命名参数 vs 位置参数
// 传统位置参数(容易混淆)
process_mesh(mesh, true, false, 0.5, 100, true, false);
// 问题:这些布尔值和数字代表什么?
// 命名参数(清晰可读)
process_mesh(mesh,
CGAL::parameters::preserve_features(true),
CGAL::parameters::edge_sensitivity(0.5),
CGAL::parameters::number_of_iterations(100));命名参数的优势
- 可读性:代码自我文档化
- 灵活性:参数顺序无关
- 可扩展:添加新参数不影响现有代码
- 可选性:轻松跳过某些参数使用默认值
19.4.2 为什么需要命名参数?
问题1:参数过多的函数
CGAL的许多算法有大量可选参数:
// 假设的网格处理函数(不使用命名参数)
template <typename Mesh>
void remesh(Mesh& mesh,
double target_edge_length,
int num_iterations = 3,
bool protect_constraints = false,
double smoothness = 0.5,
bool verbose = false,
int vertex_count_target = 0,
bool preserve_boundary = true);
// 调用时:想要修改smoothness,但必须指定前面的所有参数
remesh(mesh, 0.5, 3, false, 0.8); // 混乱!问题2:布尔参数陷阱
// 反模式:连续的布尔参数
void configure(bool enable_logging, bool use_cache, bool async_mode);
configure(true, false, true); // 难以阅读和理解问题3:向后兼容性
添加新参数时,命名参数不会破坏现有代码:
// 添加新参数 new_option
process_mesh(mesh,
CGAL::parameters::preserve_features(true));
// 新参数有默认值,不影响旧代码19.4.3 代码示例
基础示例:简单的命名参数实现
#include <iostream>
#include <string>
// ============================================
// 简单的命名参数实现
// ============================================
// 参数标签(用于标识参数类型)
struct color_tag {};
struct size_tag {};
struct visible_tag {};
// 命名参数类
template <typename Tag, typename T>
class NamedParam {
T value_;
public:
explicit NamedParam(T value) : value_(value) {}
T value() const { return value_; }
};
// 创建命名参数的辅助函数
template <typename T>
NamedParam<color_tag, T> color(T c) {
return NamedParam<color_tag, T>(c);
}
template <typename T>
NamedParam<size_tag, T> size(T s) {
return NamedParam<size_tag, T>(s);
}
template <typename T>
NamedParam<visible_tag, T> visible(T v) {
return NamedParam<visible_tag, T>(v);
}
// 参数包类(递归存储多个参数)
class NoParam {};
template <typename Tag, typename T, typename Base = NoParam>
class ParamPack : public Base {
T value_;
public:
ParamPack(T value, const Base& base = Base())
: Base(base), value_(value) {}
T get(Tag) const { return value_; }
// 添加新参数
template <typename NewTag, typename NewT>
ParamPack<NewTag, NewT, ParamPack> operator,(const NamedParam<NewTag, NewT>& np) {
return ParamPack<NewTag, NewT, ParamPack>(np.value(), *this);
}
};
// 从参数包中提取参数(带默认值)
template <typename Tag, typename T, typename Base>
T get_param(const ParamPack<Tag, T, Base>& pack, Tag, T default_val) {
return pack.get(Tag());
}
template <typename Tag, typename T>
T get_param(const NoParam&, Tag, T default_val) {
return default_val;
}
// 使用命名参数的函数
void draw_shape(const ParamPack<NoParam>& pack = NoParam()) {
// 提取参数(使用默认值)
std::string c = get_param(pack, color_tag(), std::string("red"));
int s = get_param(pack, size_tag(), 10);
bool v = get_param(pack, visible_tag(), true);
std::cout << "Drawing shape: color=" << c
<< ", size=" << s
<< ", visible=" << (v ? "true" : "false") << std::endl;
}
// 支持命名参数的模板版本
template <typename Pack>
void draw_shape(const Pack& pack) {
std::string c = get_param(pack, color_tag(), std::string("red"));
int s = get_param(pack, size_tag(), 10);
bool v = get_param(pack, visible_tag(), true);
std::cout << "Drawing shape: color=" << c
<< ", size=" << s
<< ", visible=" << (v ? "true" : "false") << std::endl;
}
// 辅助函数:开始构建参数包
template <typename Tag, typename T>
ParamPack<Tag, T> make_params(const NamedParam<Tag, T>& np) {
return ParamPack<Tag, T>(np.value());
}
int main() {
// 使用默认参数
draw_shape();
// 使用命名参数
draw_shape(make_params(color(std::string("blue"))));
// 多个命名参数
auto params = ParamPack<visible_tag, bool>(false,
ParamPack<size_tag, int>(20,
ParamPack<color_tag, std::string>("green")));
draw_shape(params);
return 0;
}中级示例:改进的命名参数系统
#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
// ============================================
// 改进的命名参数系统
// ============================================
namespace parameters {
// 参数未找到标记
struct ParamNotFound {};
// 基础参数类(无参数)
struct NoParam {
typedef NoParam base;
};
// 参数实现(递归链表结构)
template <typename Tag, typename T, typename Base = NoParam>
struct ParamImpl : Base {
T value;
ParamImpl(T v, const Base& b = Base()) : Base(b), value(std::move(v)) {}
typedef ParamImpl<Tag, T, Base> self;
typedef Base base;
};
// 参数查找辅助类
template <typename ParamPack, typename QueryTag>
struct GetParam;
// 找到匹配的标签
template <typename T, typename Tag, typename Base>
struct GetParam<ParamImpl<Tag, T, Base>, Tag> {
typedef T type;
static const T& get(const ParamImpl<Tag, T, Base>& p) { return p.value; }
};
// 继续递归查找
template <typename T, typename Tag, typename Base, typename QueryTag>
struct GetParam<ParamImpl<Tag, T, Base>, QueryTag> {
typedef typename GetParam<Base, QueryTag>::type type;
static const type& get(const ParamImpl<Tag, T, Base>& p) {
return GetParam<Base, QueryTag>::get(static_cast<const Base>(p));
}
};
// 未找到
template <typename QueryTag>
struct GetParam<NoParam, QueryTag> {
typedef ParamNotFound type;
};
// 带默认值的参数获取
template <typename ParamPack, typename Tag, typename Default>
auto get_param(const ParamPack& pack, Tag, const Default& default_val)
-> typename std::conditional<
std::is_same<typename GetParam<ParamPack, Tag>::type, ParamNotFound>::value,
Default,
typename GetParam<ParamPack, Tag>::type
>::type
{
return get_param_impl(pack, Tag(), default_val,
std::is_same<typename GetParam<ParamPack, Tag>::type, ParamNotFound>());
}
template <typename ParamPack, typename Tag, typename Default>
Default get_param_impl(const ParamPack&, Tag, const Default& default_val, std::true_type) {
return default_val;
}
template <typename ParamPack, typename Tag, typename Default>
const typename GetParam<ParamPack, Tag>::type&
get_param_impl(const ParamPack& pack, Tag, const Default&, std::false_type) {
return GetParam<ParamPack, Tag>::get(pack);
}
// 命名参数类
template <typename Tag>
struct NamedParam {
template <typename T>
ParamImpl<Tag, T> operator=(T value) const {
return ParamImpl<Tag, T>(std::move(value));
}
// 支持直接传入值(使用括号语法)
template <typename T>
ParamImpl<Tag, T> operator()(T value) const {
return ParamImpl<Tag, T>(std::move(value));
}
};
// 参数组合操作符
template <typename Tag1, typename T1, typename Tag2, typename T2>
ParamImpl<Tag2, T2, ParamImpl<Tag1, T1>>
operator,(const ParamImpl<Tag1, T1>& p1, const ParamImpl<Tag2, T2>& p2) {
return ParamImpl<Tag2, T2, ParamImpl<Tag1, T1>>(p2.value, p1);
}
// 预定义参数标签
struct color_tag {};
struct size_tag {};
struct visible_tag {};
struct opacity_tag {};
// 全局参数对象(用于命名参数语法)
constexpr NamedParam<color_tag> color;
constexpr NamedParam<size_tag> size;
constexpr NamedParam<visible_tag> visible;
constexpr NamedParam<opacity_tag> opacity;
} // namespace parameters
// ============================================
// 使用命名参数的类
// ============================================
class Shape {
public:
std::string color = "red";
int size = 10;
bool visible = true;
double opacity = 1.0;
// 默认构造函数
Shape() = default;
// 命名参数构造函数
template <typename ParamPack>
Shape(const ParamPack& params) {
using namespace parameters;
color = get_param(params, color_tag(), color);
size = get_param(params, size_tag(), size);
visible = get_param(params, visible_tag(), visible);
opacity = get_param(params, opacity_tag(), opacity);
}
void print() const {
std::cout <> "Shape: color=" << color
<< ", size=" << size
<< ", visible=" << (visible ? "true" : "false")
<< ", opacity=" << opacity << std::endl;
}
};
int main() {
using namespace parameters;
// 使用默认参数
Shape s1;
s1.print();
// 使用单个命名参数
Shape s2(color = "blue");
s2.print();
// 使用多个命名参数(任意顺序)
Shape s3((size = 20, color = "green", visible = false));
s3.print();
// 使用括号语法
Shape s4(color("purple"), opacity(0.5));
s4.print();
return 0;
}高级示例:完整的CGAL风格命名参数
#include <iostream>
#include <string>
#include <utility>
#include <type_traits>
#include <functional>
// ============================================
// 完整的CGAL风格命名参数实现
// ============================================
namespace CGAL {
namespace internal_np {
struct No_property {};
struct Param_not_found {};
// 参数实现(递归嵌套)
template <typename T, typename Tag, typename Base>
struct Named_params_impl : Base {
// 如果T可复制,直接存储;否则存储引用包装器
typename std::conditional<std::is_copy_constructible<T>::value,
T,
std::reference_wrapper<const T>>::type v;
Named_params_impl(const T& val, const Base& b) : Base(b), v(val) {}
typedef Named_params_impl<T, Tag, Base> base;
};
// 特化:基础情况
template <typename T, typename Tag>
struct Named_params_impl<T, Tag, No_property> {
typename std::conditional<std::is_copy_constructible<T>::value,
T,
std::reference_wrapper<const T>>::type v;
explicit Named_params_impl(const T& val) : v(val) {}
typedef Named_params_impl<T, Tag, No_property> base;
};
// 参数查找
template <typename ParamPack, typename QueryTag>
struct Get_param;
// 找到匹配
template <typename T, typename Tag, typename Base>
struct Get_param<Named_params_impl<T, Tag, Base>, Tag> {
typedef T type;
typedef const T& reference;
};
template <typename T, typename Tag>
struct Get_param<Named_params_impl<T, Tag, No_property>, Tag> {
typedef T type;
typedef const T& reference;
};
// 递归查找
template <typename T, typename Tag, typename Base, typename QueryTag>
struct Get_param<Named_params_impl<T, Tag, Base>, QueryTag>
: Get_param<Base, QueryTag> {};
// 未找到
template <typename QueryTag>
struct Get_param<No_property, QueryTag> {
typedef Param_not_found type;
typedef Param_not_found reference;
};
// 带默认值的查找
template <typename QueryTag, typename ParamPack, typename Default>
struct Lookup_named_param_def {
typedef typename Get_param<ParamPack, QueryTag>::type NP_type;
typedef typename Get_param<ParamPack, QueryTag>::reference NP_reference;
typedef typename std::conditional<
std::is_same<NP_type, Param_not_found>::value,
Default,
NP_type
>::type type;
typedef typename std::conditional<
std::is_same<NP_reference, Param_not_found>::value,
Default&,
NP_reference
>::type reference;
};
} // namespace internal_np
// 命名参数类
template <typename T = bool, typename Tag = internal_np::No_property, typename Base = internal_np::No_property>
struct Named_function_parameters : internal_np::Named_params_impl<T, Tag, Base> {
typedef internal_np::Named_params_impl<T, Tag, Base> base;
typedef Named_function_parameters<T, Tag, Base> self;
Named_function_parameters() : base(T()) {}
explicit Named_function_parameters(const T& v) : base(v) {}
Named_function_parameters(const T& v, const Base& b) : base(v, b) {}
// 用于SFINAE检测
typedef int CGAL_Named_function_parameters_class;
};
namespace parameters {
// 默认参数
typedef Named_function_parameters<bool, internal_np::No_property> Default_named_parameters;
inline Default_named_parameters default_values() {
return Default_named_parameters();
}
// 获取参数值
template <typename T, typename Tag, typename Base, typename QueryTag>
typename internal_np::Get_param<internal_np::Named_params_impl<T, Tag, Base>, QueryTag>::type
get_parameter(const Named_function_parameters<T, Tag, Base>& np, QueryTag) {
return get_parameter_impl(static_cast<const internal_np::Named_params_impl<T, Tag, Base>>(np), QueryTag());
}
// 实现细节
template <typename T, typename Tag, typename Base>
const T& get_parameter_impl(const internal_np::Named_params_impl<T, Tag, Base>& np, Tag) {
return np.v;
}
template <typename T, typename Tag, typename QueryTag>
internal_np::Param_not_found get_parameter_impl(const internal_np::Named_params_impl<T, Tag, internal_np::No_property>&, QueryTag) {
return internal_np::Param_not_found();
}
template <typename T, typename Tag, typename Base, typename QueryTag>
auto get_parameter_impl(const internal_np::Named_params_impl<T, Tag, Base>& np, QueryTag tag)
-> decltype(get_parameter_impl(static_cast<const Base>(np), tag)) {
return get_parameter_impl(static_cast<const Base>(np), tag);
}
// 选择参数或默认值
template <typename D>
D& choose_parameter(const internal_np::Param_not_found&, D& d) { return d; }
template <typename D>
const D& choose_parameter(const internal_np::Param_not_found&, const D& d) { return d; }
template <typename T, typename D>
T& choose_parameter(T& t, D&) { return t; }
template <typename T, typename D>
const T& choose_parameter(const T& t, const D&) { return t; }
} // namespace parameters
// 参数标签定义
namespace internal_np {
#define CGAL_NAMED_PARAMETER(NAME, TAG) \
struct TAG {}; \
inline Named_function_parameters<bool, TAG> NAME() { \
return Named_function_parameters<bool, TAG>(true); \
} \
template <typename T> \
Named_function_parameters<T, TAG> NAME(const T& t) { \
return Named_function_parameters<T, TAG>(t); \
}
CGAL_NAMED_PARAMETER(vertex_point_map, vertex_point_map_tag)
CGAL_NAMED_PARAMETER(face_normal_map, face_normal_map_tag)
CGAL_NAMED_PARAMETER(number_of_iterations, number_of_iterations_tag)
CGAL_NAMED_PARAMETER(preserve_features, preserve_features_tag)
CGAL_NAMED_PARAMETER(edge_sensitivity, edge_sensitivity_tag)
CGAL_NAMED_PARAMETER(verbose, verbose_tag)
#undef CGAL_NAMED_PARAMETER
} // namespace internal_np
// 使用SFINAE检测命名参数
template<class T>
inline constexpr bool is_named_function_parameter =
std::is_same<typename std::decay<T>::type,
Named_function_parameters<bool, internal_np::No_property>>::value;
} // namespace CGAL
// ============================================
// 使用示例
// ============================================
using namespace CGAL;
using namespace CGAL::parameters;
// 模拟网格处理函数
template <typename Mesh, typename NamedParameters = Default_named_parameters>
void process_mesh(Mesh& mesh, const NamedParameters& np = default_values()) {
// 提取参数(带默认值)
int num_iterations = choose_parameter(
get_parameter(np, internal_np::number_of_iterations_tag()),
3 // 默认值
);
bool preserve_features = choose_parameter(
get_parameter(np, internal_np::preserve_features_tag()),
false
);
double edge_sensitivity = choose_parameter(
get_parameter(np, internal_np::edge_sensitivity_tag()),
0.5
);
bool verbose = choose_parameter(
get_parameter(np, internal_np::verbose_tag()),
false
);
if (verbose) {
std::cout << "Processing mesh with parameters:" << std::endl;
std::cout << " number_of_iterations: " << num_iterations << std::endl;
std::cout << " preserve_features: " << (preserve_features ? "true" : "false") << std::endl;
std::cout << " edge_sensitivity: " << edge_sensitivity << std::endl;
}
// 处理逻辑...
std::cout << "Mesh processed successfully\!" << std::endl;
}
int main() {
struct MockMesh {};
MockMesh mesh;
// 使用默认参数
process_mesh(mesh);
std::cout << "---" << std::endl;
// 使用单个命名参数
process_mesh(mesh, number_of_iterations(10));
std::cout << "---" << std::endl;
// 使用多个命名参数(任意顺序)
process_mesh(mesh,
preserve_features(true),
edge_sensitivity(0.8),
verbose(true));
return 0;
}19.4.4 CGAL中的应用
CGAL命名参数的实际使用
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
namespace PMP = CGAL::Polygon_mesh_processing;
// 使用命名参数调用网格重划分
void remesh_example(Surface_mesh<Point_3>& mesh) {
// 清晰的参数指定
PMP::isotropic_remeshing(
faces(mesh),
0.5, // target_edge_length
mesh,
CGAL::parameters::number_of_iterations(3)
.protect_constraints(true)
.smooth_along_features(true)
);
}命名参数与BGL集成
// CGAL的BGL(Boost Graph Library)扩展使用命名参数
#include <CGAL/boost/graph/named_params.h>
// 访问顶点位置属性
template <typename PolygonMesh, typename NamedParameters>
void compute_normals(const PolygonMesh& pmesh, const NamedParameters& np) {
// 使用vertex_point_map参数获取顶点位置
typedef typename boost::lookup_named_param_def<
CGAL::vertex_point_t,
NamedParameters,
typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type
>::type VertexPointMap;
VertexPointMap vpm = boost::choose_param(
boost::get_param(np, CGAL::vertex_point_t()),
get(CGAL::vertex_point, pmesh)
);
// 使用vpm访问顶点坐标...
}19.4.5 常见陷阱
陷阱1:参数顺序依赖
// 错误:某些实现可能有参数顺序问题
auto params = param1(value1), param2(value2); // 可能有问题
// 正确:使用链式调用(如果支持)
auto params = param1(value1).param2(value2);陷阱2:类型推导问题
// 可能的问题:字符串字面量推导为const char*而非std::string
process_mesh(mesh, file_name("output.mesh")); // 可能不匹配
// 解决方案:显式指定类型或使用辅助函数
process_mesh(mesh, file_name(std::string("output.mesh")));陷阱3:引用生命周期
// 危险:引用可能悬空
auto params = vertex_point_map(create_temporary_map());
// create_temporary_map()返回的临时对象在语句结束后销毁
// 解决方案:确保参数生命周期足够长
auto map = create_map();
auto params = vertex_point_map(map);陷阱4:与函数重载混淆
// 问题:编译器可能无法区分命名参数和函数重载
void foo(int x);
void foo(const NamedParams& np);
foo(42); // 调用foo(int)
foo(param = 42); // 调用foo(NamedParams)19.4.6 最佳实践
实践1:提供合理的默认值
// 每个参数都应该有合理的默认值
template <typename NamedParameters>
void algorithm(const NamedParameters& np = parameters::default_values()) {
int iterations = choose_parameter(
get_parameter(np, number_of_iterations_tag()),
10 // 合理的默认值
);
double threshold = choose_parameter(
get_parameter(np, threshold_tag()),
0.001 // 合理的默认值
);
}实践2:文档化参数含义
/**
* @brief 网格平滑处理
* @param mesh 输入网格
* @param np 可选命名参数:
* - number_of_iterations: 迭代次数(默认:1)
* - relax_geometry: 是否松弛几何(默认:true)
* - area_threshold: 面积阈值(默认:0.0)
*/
template <typename Mesh, typename NamedParameters>
void smooth_mesh(Mesh& mesh, const NamedParameters& np);实践3:参数验证
template <typename NamedParameters>
void algorithm(const NamedParameters& np) {
int iterations = choose_parameter(
get_parameter(np, number_of_iterations_tag()), 10
);
// 验证参数有效性
CGAL_precondition(iterations > 0);
CGAL_precondition(iterations < 1000);
}实践4:向后兼容性
// 添加新参数时保持向后兼容
// 旧代码
process_mesh(mesh, target_edge_length(0.5));
// 新代码(添加新参数,不影响旧代码)
process_mesh(mesh,
target_edge_length(0.5),
new_feature(true)); // 新参数有默认值本节要点
-
命名参数提高代码可读性:特别是在参数众多、大部分有默认值的情况下。
-
实现原理:通过递归模板参数包和标签类型实现,利用编译期多态。
-
与默认参数的区别:命名参数顺序无关,可以轻松跳过某些参数。
-
CGAL的广泛应用:网格处理、点云处理等模块大量使用命名参数模式。
-
注意事项:注意参数生命周期、类型推导和向后兼容性。
-
最佳实践:提供合理默认值、文档化参数、验证参数有效性。
进一步阅读
- CGAL文档:Polygon Mesh Processing模块的Named Parameters章节
- Boost.Parameter库:命名参数的经典实现
- Fluent Interface模式:与命名参数相关的API设计模式