5

I am currently writing a moving average class.

The goal is to be able to specify the buffer size as part of the constructor when a new object of class Running_Average is created.

#include <iostream>
#include "Complex.h"
#include <cmath>
#include<stdio.h>
#include<stdlib.h>
#include <windows.h>

using namespace std;

class Running_Average
{
public:
    double sum = 0;
    double average = 0;
    int i;

    double Average(void); // Member functions declaration
    void AddSample(double);
    Running_Average(int);
};


Running_Average::Running_Average(int size) {
    int buffersize = size;
    double buffer[buffersize] = { 0 };
}

void Running_Average::AddSample(double val)  //Add new values to buffer
{
    for (i = 9; i>0; i--)
    {
        buffer[i] = buffer[i-1];
    }
    buffer[0] = val;
}

double Running_Average::Average(void) //Calculate Average of current values in buffer
{
    for (i = 0; i<buffersize; i++)
    {
        cout << buffer[i] << endl;
        sum += buffer[i];
    }
    average = sum / buffersize;
    sum = 0; 
    return average;
}

int main()
{
    double value;
    int i;
    int f = 0;
    Running_Average test;

    for (i = (40); i < (50); i++)
    {
        test.AddSample(i);
    }

    while (1) 
    {
        i = rand() % 100;
        test.AddSample(i);
        value = test.Average();
        cout << endl;
        cout << value << endl;
        cout << endl; 
        Sleep(1000);
    }

}

However, the constructor is giving me grief:

Running_Average::Running_Average(int size) {
    int buffersize = size;
    double buffer[buffersize] = { 0 };
}

Specifically:

buffer[buffersize]

throws an error in visual studio saying:

expression must have a constant size.

I want the user to specify what buffer size they want to work with when they create a new object by passing their value to the constructor.

How can I make this work without it throwing an error?

Thanks for your help!

EDIT: SOLVED! Thank you everyone for your assistance! I managed to get the function working by using the std::vector to define a variable sized array.

  • 1
    If you are not permitted to use the standard library and use the proper `std::vector` you will have to dynamically allocate a buffer using `new`. If you use `new` make sure your class follows the rule of 3. – drescherjm May 29 '18 at 03:40
  • check VLA and better use `std::vector` – Joseph D. May 29 '18 at 03:40
  • @codekaizer Unfortunately, there seems to be a lack of good canonical questions for problems like these and they end up being closed as a duplicate of some bad debugging question. There are some questions asking why variable length arrays aren't standard C++, but I don't think they count as a duplicate. – eesiraed May 29 '18 at 03:52
  • updated title to be easily identified when there are future duplicate posts relating to this. – Joseph D. May 29 '18 at 04:22
  • Possible duplicate of [Variable Length Arrays in C++14?](https://stackoverflow.com/questions/40633344/variable-length-arrays-in-c14) – Jonathan Mee May 29 '18 at 04:43

5 Answers5

4

There are plenty of ways to do this. The ones that come to mind from best to worst are :

1 Use std::vector

int buffersize = size;
std::vector<double> buffer(buffersize);

2 Built-in unique pointer or shared pointer (depending on usage)

int buffersize = size;
auto buffer = make_unique<double[]>(buffersize) // C++14

int buffersize = size;
auto buffer = make_shared<double[]>(buffersize) // C++14

3 Allocate manually

int buffersize = size;
double *buffer = new double[buffersize];

// delete [] buffer, must be called later

4 Allocate on the stack (not advised, and platform dependent)

int buffersize = size;
double *buffer = alloca(buffersize * sizeof(*buffer));

Note that in all these cases you could index buffer just like an array.

Kostas
  • 4,061
  • 1
  • 14
  • 32
  • @codekaizer It's kind of depracated, but it's a way of allocating memory on the stack dependent on runtime. People don't like it because it can kill some optimizations, or cause stack overflow. Good addition! Feel free to edit it (or I could). – Kostas May 29 '18 at 03:55
  • Hey Gill Bates (love that name) I used your first suggestion with std::vector but for whatever reason Addsample and Average methods do not recognize the existanve of the "buffer" and "buffersize" entities in the constructor anymore whereas previously it seemed like the Addsample and Average methods had access to the entities in the constructor. Any idea what could be going wrong? – Andrew Schroeder Jun 02 '18 at 18:09
  • @AndrewSchroeder I doubt the other methods had access to the constructor variables before. The only way to make the variables in your constructor accessed by other methods is making them **member/class variables**. Along with `double sum` and `doule average`, you can add `std::vector buffer`. Then you can write your constructor as: `Running_Average::Running_Average(int size) : buffer(size) { }` (initializer list). This should work. If you then want to access the size of the buffer, you can do `buffer.size()`, which is a **built-in std::vector function** – Kostas Jun 09 '18 at 13:23
3

Standard C++ doesn't have variable length arrays. (Why?) The size of an array must be a constant expression. Some compilers have non-standard extensions that allow VLAs, but you shouldn't rely on them. Use std::vector when you need an array which can have a variable length and can be resized.

eesiraed
  • 4,626
  • 4
  • 16
  • 34
3

Variable length arrays are valid in C but not in C++. In C++ you're better off using a vector collection since that allows you to better represent the intent, a varying array size, without having to maintain the current size separately.

The following complete program gives you a baseline to work from, including test harness code:

#include <iostream>
#include <vector>

class RunningValues {
public:
    RunningValues(size_t size = 50);
    void Add(double val);
    double Sum();
    double Average();

private:
    std::vector<double> dataBuffer;
    size_t sizeLimit;
    double sum;
};


// Constructor: store limit and zero sum (vector is already empty).

RunningValues::RunningValues(size_t size): sizeLimit(size), sum(0.0) {}

// Add a sample.

void RunningValues::Add(double val) {
    // Zero size, disallow adds.

    if (sizeLimit == 0) return;

    // If we would exceed limit, remove earliest.

    if (dataBuffer.size() == sizeLimit) {
        sum -= dataBuffer[0];
        dataBuffer.erase(dataBuffer.begin());
    }

    // Add value to end.

    sum += val;
    dataBuffer.push_back(val);
}

// Get the average (zero if nothing yet added) or sum.

double RunningValues::Average() {
    if (dataBuffer.size() == 0) return 0.0;
    return sum / dataBuffer.size();
}

double RunningValues::Sum() {
    return sum;
}

// Test harness.

int main() {
    RunningValues test(10);

    std::cout << "Ave = " << test.Average() << ", sum = " << test.Sum() << '\n';

    for (int i = 40; i < 50; ++i)
    {
        test.Add(i);
        std:: cout << "Add " << i << ", ave = " << test.Average() << ", sum=" << test.Sum() << '\n';
    }

    for (int i = 0; i < 20; ++i)
    {
        int val = rand() % 100;
        test.Add(val);
        std:: cout << "Add " << val << ", ave = " << test.Average() << ", sum=" << test.Sum() << '\n';
    }
}

A sample run, which shows the averages and sums at various points, is shown below:

Ave = 0, sum = 0
Add 40, ave = 40, sum=40
Add 41, ave = 40.5, sum=81
Add 42, ave = 41, sum=123
Add 43, ave = 41.5, sum=166
Add 44, ave = 42, sum=210
Add 45, ave = 42.5, sum=255
Add 46, ave = 43, sum=301
Add 47, ave = 43.5, sum=348
Add 48, ave = 44, sum=396
Add 49, ave = 44.5, sum=445
Add 83, ave = 48.8, sum=488
Add 86, ave = 53.3, sum=533
Add 77, ave = 56.8, sum=568
Add 15, ave = 54, sum=540
Add 93, ave = 58.9, sum=589
Add 35, ave = 57.9, sum=579
Add 86, ave = 61.9, sum=619
Add 92, ave = 66.4, sum=664
Add 49, ave = 66.5, sum=665
Add 21, ave = 63.7, sum=637
Add 62, ave = 61.6, sum=616
Add 27, ave = 55.7, sum=557
Add 90, ave = 57, sum=570
Add 59, ave = 61.4, sum=614
Add 63, ave = 58.4, sum=584
Add 26, ave = 57.5, sum=575
Add 40, ave = 52.9, sum=529
Add 26, ave = 46.3, sum=463
Add 72, ave = 48.6, sum=486
Add 36, ave = 50.1, sum=501

If you would rather have a solution which avoids vector (it's a bit wasteful to have all that extra functionality when the vector goes from size 0 up to N and then stays there), you can just use a naked array on the heap as a circular buffer.

The code for that is a slight variation (sans main since it hasn't changed):

#include <iostream>

class RunningValues {
public:
    RunningValues(size_t size = 50);
    ~RunningValues();
    void Add(double val);
    double Sum();
    double Average();

private:
    size_t count, next, limit;
    double sum, *data;
};

RunningValues::RunningValues(size_t size)
    : count(0), next(0), limit(size)
    , sum(0.0), data(new double[size]) {}

RunningValues::~RunningValues() {
    delete[] data;
}

void RunningValues::Add(double val) {
    // Zero size, disallow adds.

    if (limit == 0) return;

    // If we would exceed limit, remove earliest.

    if (count == limit) {
        sum -= data[next];
        --count;
    }

    // Add value to end.

    data[next] = val;
    sum += val;
    ++count;
    next = (next + 1) % limit;

}

// Get the average (zero if nothing yet added) or sum.

double RunningValues::Average() {
    if (count == 0) return 0.0;
    return sum / count;
}

double RunningValues::Sum() {
    return sum;
}

The changes from the vector-based solution are fairly minor:

  • The constructor no longer has a vector (obviously). Instead it has a fixed size array to use as a circular buffer, along with the count and next variables to manage it.
  • You now need a destructor to clean up the buffer (before, the vector managed itself).
  • the adding of items now uses count and next (rather than the vector) to figure out how to adjust the sum and keep a tally of the relevant data.
  • the calculations of average now uses count rather than the vector size.

Other than that, it's actually very similar to the vector-based code above.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
2

Based on the way you use buffer I would suggest using an std::list<double> for it.

Add this to the beginning of Running_Average:

class Running_Average
{
    private:
        list<double> buffer;

        const size_t MaxBufferSize;

    public:
        ...

The constructor :

Running_Average::Running_Average(size_t size)
:   MaxBufferSize(size)
{
}

AddSample() and Average() :

void Running_Average::AddSample(double val)
{
    if (buffer.size() == MaxBufferSize)
    {
        buffer.pop_back();
    }
    buffer.push_front(val);
}

double Running_Average::Average()
{
    double sum = 0;
    for (auto a : buffer)
    {
        cout << a << endl;
        sum += a;
    }
    return sum / buffer.size();
}

I would also remove the sum, average and i member variables and instead declare them where they are used (if needed).

Sid S
  • 6,037
  • 2
  • 18
  • 24
1

expression must have a constant size
double buffer[buffersize] = { 0 };

First, buffersize isn't a constexpr. It is a variable that changes during runtime.

When specifying a size for an array, as per the standard definition of array:

The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1,

Sample declaration of an array with 5 elements of type double should be like:

double buffer[5]; // 5 is a literal

constexpr int size = 5;
double buffer[size];    // size is constexpr

Second, buffer is a variable-length array (VLA).
VLAs are partly supported in some compilers as an extension.

How can I make this work without it throwing an error?

If you need the length to be variable, use std::vector and initialize it in your constructor:

class Running_Average {
    Running_Average(int size): buffer(size, 0) {}
    std::vector<double> buffer;
}
Joseph D.
  • 11,804
  • 3
  • 34
  • 67