Logo
Section 1 / 3

简介

C++17 最强大的语言特性之一是编译时条件语句,即 `if constexpr`。它允许你在编译时检查一个条件,并根据结果决定是否将代码继续纳入后续的编译流程。 在本章中,你将看到一个示例,展示这一新特性如何简化代码。

导入

简介

C++17 最强大的语言特性之一是编译时条件语句,即 `if constexpr`。它允许你在编译时检查一个条件,并根据结果决定是否将代码继续纳入后续的编译流程。 在本章中,你将看到一个示例,展示这一新特性如何简化代码。

问题

在《Effective Modern C++》第18条中,Scott Meyers 描述了一种名为 makeInvestment 的方法:

1template<typename... Ts> 2std::unique_ptr<Investment> 3makeInvestment(Ts&&... params);

这是一个工厂方法,用于创建 Investment 的派生类实例,其主要优势在于支持可变数量的参数!

例如,以下是建议的派生类型:

1// 基类: 2class Investment 3{ 4public: 5 virtual ~Investment() { } 6 virtual void calcRisk() = 0; 7}; 8 9class Stock : public Investment 10{ 11public: 12 explicit Stock(const std::string&) { } 13 void calcRisk() override { } 14}; 15 16class Bond : public Investment 17{ 18public: 19 explicit Bond(const std::string&, const std::string&, int) { } 20 void calcRisk() override { } 21}; 22 23class RealEstate : public Investment 24{ 25public: 26 explicit RealEstate(const std::string&, double, int) { } 27 void calcRisk() override { } 28};

书中提供的代码过于理想化,仅在所有派生类具有相同数量和类型的构造参数时才有效:

Scott Meyers:《Effective Modern C++》修订历史与勘误表:

makeInvestment 接口不现实,因为它暗示所有派生对象类型都可以用相同类型的参数创建。这一点在示例实现代码中尤为明显——参数被完美转发给所有派生类的构造函数。

例如,如果某个类的构造函数需要两个参数,而另一个类需要三个参数,那么代码可能无法编译:

1// 伪代码: 2Bond(int, int, int) { } 3Stock(double, double) { } 4make(args...) 5{ 6 if (bond) 7 new Bond(args...); 8 else if (stock) 9 new Stock(args...) 10}

现在如果你调用 make(bond, 1, 2, 3),那么 else 分支将无法编译——因为不存在 Stock(1, 2, 3) 的构造函数!要让其正常工作,我们需要一种编译期的条件判断机制,能够排除不满足条件的代码分支。

在我的博客上,借助一位读者的帮助,我们提出了一种可行的解决方案(更多内容请参见 Bartek 的编程博客:优雅的 C++ 工厂实现 2)。

可行的代码实现

1template <typename... Ts> 2unique_ptr<Investment> 3makeInvestment(const string &name, Ts&&... params) 4{ 5 unique_ptr<Investment> pInv; 6 if (name == "Stock") 7 pInv = constructArgs<Stock, Ts...>(forward<Ts>(params)...); 8 else if (name == "Bond") 9 pInv = constructArgs<Bond, Ts...>(forward<Ts>(params)...); 10 else if (name == "RealEstate") 11 pInv = constructArgs<RealEstate, Ts...>(forward<Ts>(params)...); 12// 调用其他方法初始化 pInv... 13 return pInv; 14}

真正的“魔法”发生在 constructArgs 函数内部。


核心思想是:当类型 Concrete 可以用给定的一组参数构造时,返回 unique_ptr<Concrete>;否则返回 nullptr。

使用 [[nodiscard]] 强制执行代码契约

使用 if constexpr