代码之家  ›  专栏  ›  技术社区  ›  user8510613

利用模板参数“t”的大小解决编译时的不同策略

  •  -1
  • user8510613  · 技术社区  · 6 年前

    我正试图实现一个简单的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。语义不清。

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

    1 回复  |  直到 6 年前
        1
  •  1
  •   aschepler    6 年前

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

     #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)...);
     }