MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ SFINAE在模板元编程的应用

2021-07-025.9k 阅读

C++ SFINAE 基础概念

什么是 SFINAE

SFINAE 即 “Substitution Failure Is Not An Error”,意为替换失败不是错误。在 C++ 模板实例化过程中,如果编译器为模板参数替换实参时,导致了无效的类型或表达式,但这种替换失败不会被视为编译错误,而是该模板特化被简单地忽略,编译器会继续尝试其他的模板特化或重载函数。

例如,考虑以下简单的模板函数:

template <typename T>
void f(T t) {
    // 函数体
}

// 假设存在一个结构体
struct X {};

// 尝试实例化模板函数
f(X()); 

这里编译器会为 f 函数模板中的 T 替换为 X 类型,并生成对应的函数实例。

SFINAE 的作用域

SFINAE 仅在函数模板和类模板的偏特化中起作用。对于普通函数和类模板的全特化,替换失败会导致编译错误。

例如,普通函数不能使用 SFINAE:

// 普通函数
void g(int i); 

// 尝试进行不匹配的调用,这会导致编译错误
g("hello"); 

而函数模板可以:

template <typename T>
void f(T t) {
    // 函数体
}

// 这里尝试实例化 f 函数模板时,若替换导致无效,不会报错,而是忽略该特化
f("hello"); 

SFINAE 触发条件

  1. 无效的类型:当为模板参数替换类型后,产生了无效的类型。比如在模板函数中使用了不存在的成员类型。
template <typename T>
void check_type() {
    typename T::nonexistent_type* ptr; // 这里 T::nonexistent_type 是无效类型
}

// 假设存在结构体 A
struct A {};

// 尝试实例化 check_type 模板函数
// 由于 A 没有 nonexistent_type 类型,会触发 SFINAE,该实例化被忽略
check_type<A>(); 
  1. 无效的表达式:当为模板参数替换类型后,产生了无效的表达式。例如对不支持某种运算的类型进行运算。
template <typename T>
void check_expression(T t) {
    auto result = t + 1; // 假设 T 类型不支持 + 运算,这是无效表达式
}

// 定义结构体 B
struct B {};

// 尝试实例化 check_expression 模板函数
// 由于 B 不支持 + 1 运算,触发 SFINAE,该实例化被忽略
check_expression(B()); 

C++ 模板元编程基础

模板元编程概念

模板元编程(Template Metaprogramming,TMP)是一种在编译期执行计算的编程技术。通过 C++ 的模板机制,我们可以编写在编译时生成代码的程序,而不是在运行时执行。这使得许多编译期就能确定的计算得以提前完成,提高了运行时的效率。

例如,计算阶乘可以在编译期完成:

template <int N>
struct Factorial {
    static const int value = N * Factorial<N - 1>::value;
};

template <>
struct Factorial<0> {
    static const int value = 1;
};

// 在编译期计算 5 的阶乘
const int five_factorial = Factorial<5>::value; 

这里 Factorial 模板类在编译期递归计算阶乘,five_factorial 的值在编译时就确定了。

模板元编程的优势

  1. 编译期计算:将一些复杂的计算从运行期转移到编译期,减少运行时开销。例如前面的阶乘计算,运行时直接使用编译期计算好的结果。
  2. 类型安全:模板元编程基于类型系统,在编译期进行类型检查,减少运行时类型错误的风险。例如模板函数对类型的严格匹配,可以避免运行时类型不匹配的错误。
  3. 代码生成:可以根据不同的模板参数生成不同的代码,实现代码的高度复用。例如,根据不同的数据类型生成特定的算法实现代码。

模板元编程的局限性

  1. 编译时间增加:由于模板元编程在编译期进行大量计算,可能会导致编译时间显著增加。尤其是复杂的模板元程序,编译可能会花费很长时间。
  2. 错误信息复杂:当模板元编程出现错误时,编译器给出的错误信息往往非常复杂和难以理解。因为错误信息涉及到模板实例化的复杂过程,定位和修复错误比较困难。
  3. 可维护性挑战:复杂的模板元程序代码可读性较差,维护起来相对困难。模板嵌套和递归等操作使得代码结构变得复杂,增加了理解和修改代码的难度。

SFINAE 在模板元编程中的应用

类型检测

  1. 检测类型是否有特定成员类型:我们可以利用 SFINAE 检测一个类型是否具有特定的成员类型。例如,检测一个类型是否是迭代器类型(迭代器类型通常有 value_type 成员类型)。
template <typename T, typename = void>
struct has_value_type : std::false_type {};

template <typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};

// 测试
struct MyIterator {
    using value_type = int;
};

struct NotAnIterator {};

static_assert(has_value_type<MyIterator>::value, "MyIterator should have value_type");
static_assert(!has_value_type<NotAnIterator>::value, "NotAnIterator should not have value_type");

这里通过偏特化 has_value_type 模板类,利用 std::void_t 和 SFINAE 实现了对 value_type 成员类型的检测。 2. 检测类型是否支持特定运算:检测一个类型是否支持某种运算,比如加法运算。

template <typename T, typename = void>
struct can_add : std::false_type {};

template <typename T>
struct can_add<T, std::void_t<decltype(std::declval<T>() + std::declval<T>())>> : std::true_type {};

// 测试
struct Addable {
    int data;
    Addable operator+(const Addable& other) const {
        return {data + other.data};
    }
};

struct NotAddable {};

static_assert(can_add<Addable>::value, "Addable should support addition");
static_assert(!can_add<NotAddable>::value, "NotAddable should not support addition");

这里利用 decltypestd::void_t 结合 SFINAE 检测类型是否支持加法运算。

函数重载选择

  1. 根据类型特性选择函数重载:在模板函数重载中,利用 SFINAE 根据类型的特性选择合适的函数版本。例如,有一个函数 print,对于支持 to_string 成员函数的类型,调用 to_string 输出,对于其他类型,使用 std::to_string(假设 std::to_string 适用于某些基本类型)。
template <typename T, typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, std::string>>>
std::string to_string(T t) {
    return std::to_string(t);
}

template <typename T>
std::string print(T t, std::enable_if_t<has_member_to_string<T>::value>* = nullptr) {
    return t.to_string();
}

template <typename T>
std::string print(T t, std::enable_if_t<!has_member_to_string<T>::value>* = nullptr) {
    return to_string(t);
}

// 定义一个有 to_string 成员函数的结构体
struct MyStruct {
    int data;
    std::string to_string() const {
        return std::to_string(data);
    }
};

// 测试
MyStruct s{42};
int num = 10;
std::cout << print(s) << std::endl; // 输出 42,调用 MyStruct 的 to_string
std::cout << print(num) << std::endl; // 输出 10,调用 std::to_string

这里通过 std::enable_if 和 SFINAE 实现了根据类型是否有 to_string 成员函数来选择合适的 print 函数版本。 2. 避免函数模板的不必要实例化:在某些情况下,我们可能有多个函数模板,其中一些模板在特定类型下是不适用的。利用 SFINAE 可以避免这些不适用模板的实例化。例如,有一个模板函数 process,对于指针类型有特殊处理,对于其他类型有通用处理。

template <typename T>
typename std::enable_if_t<!std::is_pointer_v<T>, void> process(T t) {
    std::cout << "Processing non - pointer type: " << t << std::endl;
}

template <typename T>
typename std::enable_if_t<std::is_pointer_v<T>, void> process(T ptr) {
    std::cout << "Processing pointer type: " << *ptr << std::endl;
}

// 测试
int num = 5;
int* ptr = &num;
process(num); // 调用非指针版本
process(ptr); // 调用指针版本

这里通过 std::enable_if 和 SFINAE 确保了对于指针类型和非指针类型分别调用合适的 process 函数模板,避免了不适用模板的实例化。

递归模板元编程优化

  1. 利用 SFINAE 终止递归:在递归模板元编程中,利用 SFINAE 可以更优雅地终止递归。例如,在一个递归计算数组元素和的模板元程序中。
template <typename T, T... values>
struct ArraySum;

template <typename T, T head, T... tail>
struct ArraySum<T, head, tail...> {
    static const T value = head + ArraySum<T, tail...>::value;
};

template <typename T, T value>
struct ArraySum<T, value> {
    static const T value = value;
};

// 测试
constexpr int sum = ArraySum<int, 1, 2, 3, 4, 5>::value;
std::cout << "Sum: " << sum << std::endl;

在这个基础上,我们可以利用 SFINAE 进行改进,使代码更健壮。比如添加对空数组的处理。

template <typename T, typename = void>
struct ArraySum;

template <typename T, T... values>
struct ArraySum<T, std::void_t<decltype((void)(values + 0)...)>> {
    static const T value = (values + ...);
};

template <typename T>
struct ArraySum<T, std::void_t<>> {
    static const T value = 0;
};

// 测试
constexpr int sum1 = ArraySum<int, 1, 2, 3, 4, 5>::value;
constexpr int sum2 = ArraySum<int>::value;
std::cout << "Sum1: " << sum1 << std::endl;
std::cout << "Sum2: " << sum2 << std::endl;

这里利用 std::void_t 和 SFINAE 处理了空数组的情况,使递归模板元编程更加完善。 2. 优化递归模板实例化路径:在复杂的递归模板元编程中,可能存在多种递归路径,利用 SFINAE 可以选择更优的递归路径。例如,在一个模板元程序中,根据类型的不同特性选择不同的递归计算方式。

template <typename T, bool IsBigType>
struct ComplexCalculation;

template <typename T>
struct ComplexCalculation<T, true> {
    static T calculate(T t) {
        // 针对大类型的复杂计算
        return t * t;
    }
};

template <typename T>
struct ComplexCalculation<T, false> {
    static T calculate(T t) {
        // 针对小类型的简单计算
        return t + t;
    }
};

template <typename T>
typename std::enable_if_t<std::is_floating_point_v<T>, T> complex_calculate(T t) {
    return ComplexCalculation<T, sizeof(T) > 4>::calculate(t);
}

template <typename T>
typename std::enable_if_t<!std::is_floating_point_v<T>, T> complex_calculate(T t) {
    return ComplexCalculation<T, sizeof(T) > 2>::calculate(t);
}

// 测试
float f = 2.0f;
int i = 3;
std::cout << "Complex calculation for float: " << complex_calculate(f) << std::endl;
std::cout << "Complex calculation for int: " << complex_calculate(i) << std::endl;

这里通过 std::enable_if 和 SFINAE 根据类型是否为浮点数以及类型大小选择不同的递归计算方式,优化了递归模板实例化路径。

结合标准库工具强化 SFINAE 应用

与 std::enable_if 配合

  1. 函数模板条件启用std::enable_if 是 SFINAE 的常用辅助工具,用于根据条件启用或禁用函数模板。例如,实现一个函数模板 square,只有当类型 T 是算术类型时才启用。
template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T square(T t) {
    return t * t;
}

// 测试
int num = 5;
std::cout << "Square of int: " << square(num) << std::endl;

// 假设存在结构体 S
struct S {};
// 以下调用会因为 S 不是算术类型而触发 SFINAE,编译不通过
// std::cout << "Square of S: " << square(S()) << std::endl; 

这里 std::enable_if_t<std::is_arithmetic_v<T>> 作为模板参数的默认值,只有当 T 是算术类型时,square 函数模板才会被实例化。 2. 类模板条件特化:在类模板中,std::enable_if 也可以用于条件特化。例如,有一个类模板 MyContainer,对于整数类型有特殊的存储和操作方式。

template <typename T, typename = void>
class MyContainer {
    // 通用实现
    T data;
public:
    MyContainer(T t) : data(t) {}
    T get() const {
        return data;
    }
};

template <typename T>
class MyContainer<T, std::enable_if_t<std::is_integral_v<T>>> {
    std::vector<T> data;
public:
    MyContainer(T t) {
        data.push_back(t);
    }
    T get() const {
        return data[0];
    }
};

// 测试
MyContainer<int> int_container(5);
MyContainer<float> float_container(2.5f);
std::cout << "Int container value: " << int_container.get() << std::endl;
std::cout << "Float container value: " << float_container.get() << std::endl;

这里通过 std::enable_if 实现了 MyContainer 类模板对于整数类型的条件特化。

与 std::void_t 配合

  1. 简化类型检测std::void_t 与 SFINAE 结合可以简化类型检测。例如,检测一个类型是否有特定成员函数。
template <typename T, typename = void>
struct has_member_function : std::false_type {};

template <typename T>
struct has_member_function<T, std::void_t<decltype(std::declval<T>().member_function())>> : std::true_type {};

// 定义一个有 member_function 成员函数的结构体
struct HasFunction {
    void member_function() {}
};

// 定义一个没有 member_function 成员函数的结构体
struct NoFunction {};

static_assert(has_member_function<HasFunction>::value, "HasFunction should have member_function");
static_assert(!has_member_function<NoFunction>::value, "NoFunction should not have member_function");

这里 std::void_t 用于在 SFINAE 中简化类型检测,通过 decltype 检查成员函数是否存在。 2. 辅助模板元编程结构:在复杂的模板元编程结构中,std::void_t 可以辅助构建更灵活的模板结构。例如,在一个类型特征检测模板体系中。

template <typename... Ts>
using void_t = std::void_t<Ts...>;

template <typename T, typename = void>
struct is_container : std::false_type {};

template <typename T>
struct is_container<T, void_t<typename T::value_type, decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> : std::true_type {};

// 测试
std::vector<int> vec;
int num = 5;
static_assert(is_container<std::vector<int>>::value, "std::vector should be a container");
static_assert(!is_container<int>::value, "int should not be a container");

这里 void_t 用于构建 is_container 模板类,检测一个类型是否为容器类型。

与 std::conditional 配合

  1. 根据条件选择类型std::conditional 与 SFINAE 结合可以根据条件选择不同的类型。例如,在一个模板函数中,根据类型是否为指针选择不同的返回类型。
template <typename T>
typename std::conditional<std::is_pointer_v<T>, T, const T&>::type get_value(T t) {
    return t;
}

// 测试
int num = 5;
int* ptr = &num;
std::cout << "Value from int: " << get_value(num) << std::endl;
std::cout << "Value from pointer: " << *get_value(ptr) << std::endl;

这里 std::conditional 根据 T 是否为指针类型,选择不同的返回类型,结合 SFINAE 确保模板函数在不同类型下的正确行为。 2. 构建类型相关的模板结构:在模板元编程中,利用 std::conditional 和 SFINAE 可以构建复杂的类型相关的模板结构。例如,构建一个根据类型特性选择不同存储方式的模板类。

template <typename T>
class TypeAwareStorage {
    using storage_type = typename std::conditional<std::is_fundamental_v<T>, T, std::unique_ptr<T>>::type;
    storage_type data;
public:
    TypeAwareStorage(T t) {
        if constexpr (std::is_fundamental_v<T>) {
            data = t;
        } else {
            data = std::make_unique<T>(t);
        }
    }

    T get() const {
        if constexpr (std::is_fundamental_v<T>) {
            return data;
        } else {
            return *data;
        }
    }
};

// 测试
TypeAwareStorage<int> int_storage(5);
TypeAwareStorage<std::string> string_storage("hello");
std::cout << "Int value: " << int_storage.get() << std::endl;
std::cout << "String value: " << string_storage.get() << std::endl;

这里 std::conditional 结合 SFINAE 帮助 TypeAwareStorage 模板类根据类型是否为基本类型选择不同的存储方式。

实际项目中 SFINAE 在模板元编程的应用案例

通用算法库设计

  1. 根据容器类型选择最优算法:在一个通用的排序算法库中,利用 SFINAE 和模板元编程根据容器类型选择最优的排序算法。例如,对于 std::vector 可以使用快速排序,对于 std::list 可以使用归并排序。
template <typename Container, typename = void>
struct SortAlgorithm {
    static void sort(Container& c) {
        // 通用排序实现,例如插入排序
        for (auto it = std::next(c.begin()); it != c.end(); ++it) {
            auto key = *it;
            auto j = it;
            while (j != c.begin() && *std::prev(j) > key) {
                *j = *std::prev(j);
                --j;
            }
            *j = key;
        }
    }
};

template <typename T>
struct SortAlgorithm<std::vector<T>, std::void_t<decltype(std::declval<std::vector<T>>().begin()), decltype(std::declval<std::vector<T>>().end())>> {
    static void sort(std::vector<T>& v) {
        std::sort(v.begin(), v.end());
    }
};

template <typename T>
struct SortAlgorithm<std::list<T>, std::void_t<decltype(std::declval<std::list<T>>().begin()), decltype(std::declval<std::list<T>>().end())>> {
    static void sort(std::list<T>& l) {
        std::vector<T> temp(l.begin(), l.end());
        std::sort(temp.begin(), temp.end());
        l.assign(temp.begin(), temp.end());
    }
};

// 测试
std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
std::list<int> list = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
SortAlgorithm<std::vector<int>>::sort(vec);
SortAlgorithm<std::list<int>>::sort(list);
for (int i : vec) {
    std::cout << i << " ";
}
std::cout << std::endl;
for (int i : list) {
    std::cout << i << " ";
}
std::cout << std::endl;

这里通过 SFINAE 和模板偏特化,根据容器类型为 std::vectorstd::list 选择了更适合的排序算法。 2. 适配不同容器接口:在通用算法库中,有些算法可能需要特定的容器接口。利用 SFINAE 可以确保算法只应用于具有合适接口的容器。例如,一个需要随机访问迭代器的算法。

template <typename Container, typename = void>
struct RandomAccessAlgorithm {
    static void execute(Container& c) {
        // 这里是不适用该算法的提示
        static_assert(false, "Container does not support random access iterators");
    }
};

template <typename T>
struct RandomAccessAlgorithm<std::vector<T>, std::void_t<decltype(std::declval<std::vector<T>>().begin() + 0)>> {
    static void execute(std::vector<T>& v) {
        // 这里是基于随机访问迭代器的算法实现
        for (size_t i = 0; i < v.size(); ++i) {
            v[i] *= 2;
        }
    }
};

// 测试
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::list<int> list1 = {1, 2, 3, 4, 5};
RandomAccessAlgorithm<std::vector<int>>::execute(vec1);
// 以下调用会触发 SFINAE,编译不通过
// RandomAccessAlgorithm<std::list<int>>::execute(list1); 
for (int i : vec1) {
    std::cout << i << " ";
}
std::cout << std::endl;

这里通过 SFINAE 确保 RandomAccessAlgorithm 只应用于支持随机访问迭代器的容器,如 std::vector

类型安全的接口封装

  1. 基于类型特性的接口选择:在一个图形库中,不同的图形对象可能有不同的绘制接口。利用 SFINAE 和模板元编程可以根据对象类型选择合适的绘制接口。例如,对于简单的矩形对象可以直接绘制,对于复杂的 3D 模型可能需要更复杂的渲染流程。
struct Rectangle {
    int x, y, width, height;
};

class Model3D {
    // 复杂的 3D 模型数据和方法
};

template <typename T, typename = void>
struct Drawer {
    static void draw(T& obj) {
        static_assert(false, "Unsupported object type for drawing");
    }
};

template <>
struct Drawer<Rectangle, std::void_t<decltype(std::declval<Rectangle>().x), decltype(std::declval<Rectangle>().y), decltype(std::declval<Rectangle>().width), decltype(std::declval<Rectangle>().height)>> {
    static void draw(Rectangle& rect) {
        std::cout << "Drawing rectangle at (" << rect.x << ", " << rect.y << ") with width " << rect.width << " and height " << rect.height << std::endl;
    }
};

template <>
struct Drawer<Model3D, std::void_t<decltype(std::declval<Model3D>().render())>> {
    static void draw(Model3D& model) {
        std::cout << "Rendering 3D model" << std::endl;
        model.render();
    }
};

// 测试
Rectangle rect{10, 10, 100, 50};
Model3D model;
Drawer<Rectangle>::draw(rect);
Drawer<Model3D>::draw(model);

这里通过 SFINAE 和模板特化,根据图形对象的类型选择了合适的绘制接口。 2. 接口兼容性检查:在库的开发中,确保外部传入的类型与库的接口兼容是很重要的。利用 SFINAE 可以在编译期检查类型是否满足接口要求。例如,在一个数学计算库中,要求传入的类型支持特定的数学运算。

template <typename T, typename = void>
struct MathOperationChecker {
    static_assert(false, "Type does not support required math operations");
};

template <typename T>
struct MathOperationChecker<T, std::void_t<decltype(std::declval<T>() + std::declval<T>()), decltype(std::declval<T>() - std::declval<T>()), decltype(std::declval<T>() * std::declval<T>())>> {
    static void check() {
        // 类型满足要求,不做实际操作
    }
};

// 定义一个满足要求的结构体
struct MathType {
    int data;
    MathType operator+(const MathType& other) const {
        return {data + other.data};
    }
    MathType operator-(const MathType& other) const {
        return {data - other.data};
    }
    MathType operator*(const MathType& other) const {
        return {data * other.data};
    }
};

// 测试
MathOperationChecker<MathType>::check();
// 假设存在结构体 NotMathType
struct NotMathType {};
// 以下调用会触发 SFINAE,编译不通过
// MathOperationChecker<NotMathType>::check(); 

这里通过 SFINAE 检查类型是否支持特定的数学运算,确保了库接口的类型安全性。

代码生成与优化

  1. 编译期代码生成:在一个网络库中,根据不同的协议类型(如 TCP、UDP)在编译期生成不同的代码。利用 SFINAE 和模板元编程可以实现这种代码生成。例如,对于 TCP 协议可能需要更复杂的连接管理代码,对于 UDP 协议可能更注重数据的快速发送。
template <typename Protocol, typename = void>
class NetworkConnection {
public:
    static_assert(false, "Unsupported protocol");
};

template <>
class NetworkConnection<struct TCP, std::void_t<>> {
public:
    void connect() {
        std::cout << "Connecting via TCP" << std::endl;
        // 复杂的 TCP 连接逻辑
    }
    void send(const char* data, size_t size) {
        std::cout << "Sending data via TCP: " << std::string(data, size) << std::endl;
        // TCP 发送逻辑
    }
};

template <>
class NetworkConnection<struct UDP, std::void_t<>> {
public:
    void send(const char* data, size_t size) {
        std::cout << "Sending data via UDP: " << std::string(data, size) << std::endl;
        // UDP 发送逻辑
    }
};

// 测试
NetworkConnection<TCP> tcp_conn;
NetworkConnection<UDP> udp_conn;
tcp_conn.connect();
tcp_conn.send("Hello, TCP", 10);
udp_conn.send("Hello, UDP", 9);

这里通过 SFINAE 和模板特化,根据协议类型在编译期生成了不同的 NetworkConnection 类的实现。 2. 性能优化:在一些数值计算库中,利用 SFINAE 和模板元编程可以根据数据类型和平台特性进行性能优化。例如,在支持 SIMD 指令集的平台上,对于浮点类型数组的计算可以使用 SIMD 指令加速。

#ifdef _M_IX86_FP
#include <immintrin.h>
template <typename T, typename = void>
struct NumericalCalculation {
    static void compute(T* data, size_t size) {
        for (size_t i = 0; i < size; ++i) {
            data[i] *= 2;
        }
    }
};

template <>
struct NumericalCalculation<float, std::void_t<decltype(_mm256_set_ps(0.f))>> {
    static void compute(float* data, size_t size) {
        __m256 factor = _mm256_set1_ps(2.f);
        for (size_t i = 0; i < size; i += 8) {
            __m256 values = _mm256_loadu_ps(data + i);
            values = _mm256_mul_ps(values, factor);
            _mm256_storeu_ps(data + i, values);
        }
    }
};
#else
template <typename T, typename = void>
struct NumericalCalculation {
    static void compute(T* data, size_t size) {
        for (size_t i = 0; i < size; ++i) {
            data[i] *= 2;
        }
    }
};
#endif

// 测试
float float_data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
NumericalCalculation<float>::compute(float_data, 8);
for (float f : float_data) {
    std::cout << f << " ";
}
std::cout << std::endl;

这里通过 SFINAE 和条件编译,根据平台是否支持 SIMD 指令集,为浮点类型选择了不同的计算方式,实现了性能优化。

在实际项目中,SFINAE 与模板元编程的结合为代码的通用性、类型安全性和性能优化提供了强大的支持,使得 C++ 代码能够更好地适应不同的需求和场景。