实现快速模板阵列重置方法

时间:2014-06-17 08:16:28

标签: c++ templates metaprogramming typetraits

我有一个带有重置方法的模板化数组类。我想优化重置代码以在POD上使用memset,并在非POD上调用用户提供的reset()。

我想要(1)方法(重置)的相同名称,并根据模板参数使用相关实现,以及(2)如果使用非pod类型T而没有reset()函数,则编译时错误

template <typename T>
class CArray {
private:
    T* array;
    size_t size;
public:
    void reset() {
        memset(array, 0, size);
    }
    void reset() {
        for (size_t i=0; i<size; ++i)
            array[i].reset();
    }
}

CArray<int> arr1;
arr1.reset();                       -> will use memset and be fast
struct t2 { int* mem; void reset() {} };
CArray<t2> arr2;
arr2.reset();                      -> will call t2.reset for each element
struct t3 { int* mem; };
CArray<t3> arr3;
arr3.reset();                      -> should fail compilation since t3.reset() is missing

我尝试过使用type_traits,但我不确定这是否正确(可以帮助编译时检查)吗?

3 个答案:

答案 0 :(得分:2)

template <typename T>
class CArray {
private:
    T* array;
    size_t size;
public:
    void reset() {
        CArrayReset( this );
    }
    void MemberReset()
    {
        for (size_t i=0; i<size; ++i)
            array[i].reset();
    }
    void MemsetReset()
    {
        memset(array, 0, size);
    }
};

template <typename T>
typename disable_if<is_pod<T>, void>::type
CArrayReset( CArray<T>* arr )
{
    arr->MemberReset();
}

template <typename T>
typename enable_if<is_pod<T>, void>::type
CArrayReset( CArray<T>* arr )
{
    arr->MemsetReset();
}

void tryitout()
{
    CArray<int> arr1;
    arr1.reset();                     // will use memset and be fast
    struct t2 { int* mem; void reset() {} };
    CArray<t2> arr2;
    arr2.reset();                     // will call t2.reset for each element
    struct t3 { virtual void foo(){} int* mem; }; // added virtual func to make it non pod
    CArray<t3> arr3;
    arr3.reset();                     // will not compile
}

(省略各种标题)

答案 1 :(得分:1)

试图在POD上强制memset似乎是一个毫无意义的“优化”IMO

#include <array>
#include <cstring>

struct bar
{
  int i;
  int j;
};

std::array<bar, 4> test;

void foo()
{
  memset(&test, 0, sizeof test);
}

void foo2()
{
  test.fill(bar());
}

使用-O3 -c -S进行编译可以得到:

__Z3foov:
LFB944:
    movq    $0, _test(%rip)
    movq    $0, 8+_test(%rip)
    movq    $0, 16+_test(%rip)
    movq    $0, 24+_test(%rip)
    ret

...

__Z4foo2v:
LFB945:
    pxor    %xmm0, %xmm0
    movaps  %xmm0, _test(%rip)
    movaps  %xmm0, 16+_test(%rip)
    ret

专注于编写易于理解的代码,让编译器负责优化。只要您的类具有默认构造函数,您就不需要使用SFINAE。

对于您的课程,reset方法可以按以下方式工作

void reset() {
  std::fill_n(array, size, T());
}

答案 2 :(得分:1)

不能在类模板的非模板成员函数上使用SFINAE,因为这将部分地特化一个函数,这是不允许的(感谢{{3}的dyp })。

然而,您可以非常轻松地使用标签调度,正如该文章所暗示的那样。

演示代码。请注意,我已将阵列更改为使用std::vector进行内存管理,以使我的演示更简单(实际上还不清楚为什么您的阵列不会这样做!)

#include <iostream>
#include <vector>
#include <string>
#include <type_traits>

struct Baz
{
    void reset() { std::cout << "Baz reset" << std::endl; }
    Baz(int x = 1) { }
};

template <typename T>
struct CArray {   
    std::vector<T> ptr;

    void reset()
    {
        do_reset( std::is_pod<T>{} );
    }

private:
    void do_reset(std::true_type) { std::fill(ptr.begin(), ptr.end(), T()); }
    void do_reset(std::false_type) { for (auto &x : ptr) x.reset(); }
};

int main()
{
    CArray<int> x;
    x.ptr.push_back(1);
    x.reset();      // OK, no output

    CArray<std::string> y;
    // y.reset();   // compilation error - std::string has no member "reset"

    CArray<Baz> z;
    z.ptr.push_back( Baz() );
    z.reset();      // OK, output "Baz reset"
}