【问题标题】:Implementing fast template array reset method实现快速模板数组重置方法
【发布时间】:2014-06-17 08:16:27
【问题描述】:

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

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

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,但我不确定这是否是正确的方向(它对编译时检查有帮助吗)?

【问题讨论】:

  • boost 有一个enable_if,某种 SFINAE on POD-ness maby?
  • 你可以在std::is_pod&lt;T&gt;上使用SFINAE
  • 您可能想要“简单可复制”而不是 POD。不管怎样,C++14 有一系列有趣的模板,例如is_podis_trivially_copyable 等。实现留作练习等...
  • 与 user657267 的观点一致,即memset 可能等于或差于std::fill;另一种选择是通过 SFINAE 调用 reset()(如果存在)和 std::fill(如果不存在)。

标签: c++ templates metaprogramming typetraits


【解决方案1】:
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
}

(省略各种标题)

【讨论】:

  • 您的代码中有两个void MemberReset() 成员函数。
  • 开,抱歉。他们和我太像了。
【解决方案2】:

试图在 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());
}

【讨论】:

  • 可能是真的;但是他想为非 POD 调用成员函数 .reset() 所以他的问题仍然存在
  • @MattMcNabb 好吧,尽管我认为一旦您不再需要区分 POD 和非 POD,问题就会消失,假设他想将包含的数据重置为默认状态,即他应该为更复杂的类提供默认构造函数,而不是 reset 函数。
  • 这是来自 c++11 的一个惊人的功能。可惜我受限于VC10和CArray目前的数据存储方式(哑T*)
  • @liorda 你不必使用std::array,如果你想要堆栈上的内存,你也可以使用任何其他容器或标准数组和std::fill,甚至只是循环遍历数组。关键是memset 可能不是答案。
  • @user657267 也很正确;从默认构造的 xvalue 迁移应该具有与 reset 一样好的性能。当然,除非“重置”除了将对象恢复到其默认状态之外做其他事情,例如计算对象被重置的次数!
【解决方案3】:

不可能在类模板的非模板成员函数上使用 SFINAE,因为这会部分特化一个函数,这是不允许的(感谢 dyp 提供the explanation)。

但是,正如那篇文章所建议的那样,您可以很容易地使用 标签调度

演示代码。请注意,我已将数组更改为使用 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"
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-20
    • 1970-01-01
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多