将自定义C ++容器的大小设置为模板参数与构造函数

时间:2016-09-06 20:22:58

标签: c++ templates containers

我在C ++中编写了一个固定大小的容器(确切地说是一个环形缓冲区)。目前我在构造函数中设置容器的大小,然后在堆上分配实际的缓冲区。但是,我一直在考虑将size参数从构造函数中移出到模板中。

从这里开始(RingBuffer拟合100个整数)

RingBuffer<int> buffer(size);

到这个

RingBuffer<int, 100> buffer;

据我所知,这将允许我在堆栈上分配整个缓冲区,这比堆分配更快。主要是可读性和可维护性问题。这些缓冲区通常显示为类的成员。我必须用大小初始化它们,所以我必须在类的每个构造函数的初始化列表中初始化它们。这意味着如果我想更改RingBuffer的容量,我必须记住在每个初始化列表中更改它,或者使用笨拙的static const int BUFFER_SIZE = 100;成员变量。

我的问题是,将容器大小指定为模板参数是否有任何缺点,而不是在构造函数中?这两种方法的优点和缺点是什么?

据我所知,编译器将为每个不同大小的RingBuffer生成一个新类型。这可能会变成很多。这会伤害编译时间吗?它会使代码膨胀还是阻止优化?当然我知道这很大程度上取决于确切的用例,但做出这个决定时我需要注意哪些事项?

2 个答案:

答案 0 :(得分:2)

  

我的问题是,将容器大小指定为模板参数是否有任何缺点,而不是在构造函数中?这两种方法的优点和缺点是什么?

如果您将size作为模板参数,那么它需要是constexpr(编译时常量表达式)。因此,您的缓冲区大小不能取决于任何运行时特征(如用户输入)。

作为一个编译时常量为一些优化打开了大门(循环展开和常量折叠让我更加高效)。

  

据我所知,编译器会为每个不同大小的RingBuffer生成一个新类型。

这是事实。但我不担心这一点,因为拥有许多不同类型本身不会对性能或代码大小产生任何影响(但可能在编译时)。

  

这对编译时间有多大影响吗?

它会使编译速度变慢。虽然我怀疑在你的情况下(这是一个非常简单的模板),这甚至会引人注意。因此,这取决于你对&#34;多&#34;。

的定义
  

是否会使代码膨胀或阻止优化?

防止优化?没有。膨胀代码?有可能。这取决于您实现类的具体程度以及编译器的功能。例如:

template<size_t N>
struct Buffer {
  std::array<char, N> data;

  void doSomething(std::function<void(char)> f) {
    for (size_t i = 0; i < N; ++i) {
       f(data[i]);
    }
  }
  void doSomethingDifferently(std::function<void(char)> f) {
    doIt(data.data(), N, f);
  }
};

void doIt(char const * data, size_t size, std::function<void(char)> f) {
  for (size_t i = 0; i < size; ++i) {
    f(data[i]);
  }
}

doSomething可能会被编译为(可能完全)展开的循环代码,并且您有Buffer<100>::doSomethingBuffer<200>::doSomething等等,每个都可能是一个大函数。 doSomethingDifferently可能只编译成一个简单的跳转指令,因此拥有多个这些指令并不是一个大问题。虽然您的编译器也可以将doSomething更改为类似doSomethingDifferently,但反之亦然。

所以最后:

请勿尝试做出此决定取决于性能,优化,编译时间或代码膨胀。 确定在您的情况下哪些更有意义。是否只有编译时间已知大小的缓冲区?

此外:

  

这些缓冲区通常显示为类的成员。我必须用大小初始化它们,所以我必须在类的每个构造函数的初始化列表中初始化它们。

你知道&#34;委派施工人员&#34;?

答案 1 :(得分:2)

正如Daniel Jour已经说过,代码膨胀不是一个大问题,如果需要可以处理。 将大小设置为constexpr的好处是,它允许您在编译时检测到一些错误,否则这些错误将在运行时发生。

  

据我所知,这将允许我在堆栈上分配整个缓冲区,这比堆分配更快。   这些缓冲区通常显示为类

的成员

只有在自动内存中分配了拥有类时才会发生这种情况。通常情况并非如此。请考虑以下示例:

struct A {
    int myArray[10];
};

struct B {
    B(): dynamic(new A()) {}

    A automatic; // should be in the "stack"
    A* dynamic; // should be in the "heap"
};

int main() {
    B b1;
    b1;                         // automatic memory
    b1.automatic;               // automatic memory
    b1.automatic.myArray;       // automatic memory
    b1.dynamic;                 // automatic memory
    (*b1.dynamic);              // dynamic memory
    (*b1.dynamic).myArray;      // dynamic memory

    B* b2 = new B();
    b2;                         // automatic memory
    (*b2);                      // dynamic memory
    (*b2).automatic;            // dynamic memory
    (*b2).automatic.myArray;    // dynamic memory
    (*b2).dynamic;              // dynamic memory
    (*(*b2).dynamic).myArray;   // dynamic memory
}