首页 > 解决方案 > 通过模板参数“T”的大小在编译时解析不同的策略

问题描述

我正在尝试实现一个简单的 GC 系统以用于学习目的。基本上,它将newElement为用户提供一个“新建”对象的界面。

我要解决的策略是根据对象类型通过不同的策略分配对象(小对象的池/大对象的malloc ...),例如:

T* newElement(Args&&... args)
{
    if(sizeof(T) < 4){
        // strategy1
        return newImpl1(std::forward<Args>(args)...);
    } else if(4 <= sizeof(T) < 16){
        // strategy2
        return newImpl2(std::forward<Args>(args)...);
    } else{
        // strategy3
        return newImpl3(std::forward<Args>(args)...);
    }
}

我认为这个成本可以在编译时而不是运行时,因为sizeof(T)可以在编译时评估。我知道在 C++17 中,我们有constexpr if处理这种情况的特性。但是,我正在处理 VS 2015,它只支持 C++11 和 C++14。所以,我认为这个过程是两个不同的阶段:

  1. “新”应该接受不同类型的标签(按类型)来解决不同的策略

  2. “调度”应该接受 T 作为输入,并有办法输出正确的标签(按类型)

通常,阶段 2 的目的是通过一系列条件表达式输出不同类型的标签(无论是值还是类型)​​。

我想到了两种解决方案。

enum class Strategy {
    small, middle, big
};

constexpr size_t SmallMiddleThreshold = 4;
constexpr size_t MiddleBigThreshold = 8;

template
<Strategy s=Strategy::small>
struct newImpl {
    template
    <typename T, typename... Args>
    static T* apply(Args&&... args)
    {
        cout << "small!" << endl;
        return new T(std::forward<Args>(args)...);
    }
};

template
<>
struct newImpl<Strategy::middle> {
    template
    <typename T, typename... Args>
    static T* apply(Args&&... args)
    {
        cout << "middle!" << endl;
        return new T(std::forward<Args>(args)...);
    }
};

template
<>
struct newImpl<Strategy::big> {
    template
    <typename T, typename... Args>
    static T* apply(Args&&... args)
    {
        cout << "big!" << endl;
        return new T(std::forward<Args>(args)...);
    }
};

解决方案 1

使用可变参数模板来扩展条件。

template
<bool Condition1=true, bool... Conditions>
struct SizeDispatcher1 {
    constexpr static Strategy value = Strategy::small;
};

template
<bool... Conditions>
struct SizeDispatcher1<false, Conditions...> {
    constexpr static Strategy value = SizeDispatcher2<Conditions...>::value;
};

template
<bool Condition2 = true, bool... Conditions>
struct SizeDispatcher2 {
    constexpr static Strategy value = Strategy::middle;
};

template
<bool... Conditions>
struct SizeDispatcher2<false, Conditions...> {
    constexpr static Strategy value = SizeDispatcher3<Conditions...>::value;
};

template
<bool Condition3 = true, bool... Conditions>
struct SizeDispatcher3 {
    constexpr static Strategy value = Strategy::big;
};

template
<typename T>
struct SizeDispatcher {
    constexpr static Strategy value = 
        SizeDispatcher1< 
            sizeof(T) < SmallMiddleThreshold,
            SmallMiddleThreshold <= sizeof(T) && sizeof(T) < MiddleBigThreshold,
            MiddleBigThreshold <= sizeof(T)
        >::value;
};

template
<typename T, typename... Args>
T* newElement(Args&&... args)
{
    return newImpl<SizeDispatcher<T>::value>::apply<T>(std::forward<Args>(args)...);
}

解决方案 2

使用偏特化来匹配不同的情况。

template
<bool Condition1=true, bool Condition2=false, bool Condition3=false>
struct SizeDispatcherImpl {
    constexpr static Strategy value = Strategy::small;
};

template
<>
struct SizeDispatcherImpl<false, true, false> {
    constexpr static Strategy value = Strategy::middle;
};

template
<>
struct SizeDispatcherImpl<false, false, true> {
    constexpr static Strategy value = Strategy::big;
};

template
<typename T>
struct SizeDispatcher {
    constexpr static Strategy value = 
        SizeDispatcherImpl< 
            sizeof(T) < SmallMiddleThreshold,
            SmallMiddleThreshold <= sizeof(T) && sizeof(T) < MiddleBigThreshold,
            MiddleBigThreshold <= sizeof(T)
        >::value;
};

但是,我对上述代码有一些疑问。

首先,它能否正确满足我的要求?也就是在编译时解决不同的策略?

其次,这两种解决方案都至少有以下缺点: 1.“调度程序”与条件表达式(格式、序列...)紧密耦合,这绝对不是编码的好习惯。2.语义不清晰。

那么,如果可能的话,如何正确更好地解决这个问题?(通过一系列条件表达式产生不同的标签)

标签: c++c++11templates

解决方案


这些解决方案看起来确实有点复杂。另一种解决方案是使用重载的辅助函数,甚至可能重载newElement自身,但有 SFINAE 限制。一个好处是可以在实现旁边看到条件。

 #include <type_traits>
 #include <iostream>

 template <typename T, typename... Args, std::enable_if_t<(sizeof(T) < 4)>* = nullptr>
 T* newElement(Args&& ... args) {
     std::cout << "small!" << std::endl;
     return new T(std::forward<Args>(args)...);
 }

 template <typename T, typename... Args,
     std::enable_if_t<(sizeof(T) >= 4 && sizeof(T) < 16)>* = nullptr>
 T* newElement(Args&& ... args) {
     std::cout << "middle!" << std::endl;
     return new T(std::forward<Args>(args)...);
 }

 template <typename T, typename... Args, std::enable_if_t<(sizeof(T) >= 16)>* = nullptr>
 T* newElement(Args&& ... args) {
     std::cout << "big!" << std::endl;
     return new T(std::forward<Args>(args)...);
 }

推荐阅读