4

A problem set for people learning C++ is

Write a short program to simulate a ball being dropped off of a tower. To start, the user should be asked for the initial height of the tower in meters. Assume normal gravity (9.8 m/s2), and that the ball has no initial velocity. Have the program output the height of the ball above the ground after 0, 1, 2, 3, 4, and 5 seconds. The ball should not go underneath the ground (height 0).

Before starting C++ I had a reasonable, but primarily self taught, knowledge of Java. So looking at the problem it seems like it ought to be split into

  • input class
  • output class
  • calculations class
  • Physical constants class (recommended by the question setter)
  • controller ('main') class

The input class would ask the user for a starting height, which would be passed to the controller. The controller would give this and a number of seconds (5) to the calculations class, which would create an array of results and return this to the controller. The controller would hand the array of results to the output class that would print them to the console.

I will put the actual code at the bottom, but it's possibly not needed.

You can probably already see the problem, attempting to return an array. I'm not asking how to get round that problem, there is a workaround here and here. I'm asking, is the problem a result of bad design? Should my program be structured differently, for performance, maintenance or style reasons, such that I would not be attempting to return an array like object?

Here is the code (which works apart from trying to return arrays);

main.cpp

/*
 * Just the main class, call other classes and passes variables around
 */
#include <iostream>
#include "dropSim.h"
using namespace std;

int main()
{
    double height = getHeight();
    int seconds = 5;
    double* results = calculateResults(height, seconds);
    outputResults(results);
    return 0;
}

getHeight.cpp

/*
 *  Asks the user for a height from which to start the experiment
 *  SI units
 */
#include <iostream>
using namespace std;

double getHeight()
{
    cout << "What height should the experiment start at; ";
    double height;
    cin >> height;
    return height;
}

calculateResults.cpp

/*
 * given the initial height and the physical constants, the position of the ball
 * is calculated at integer number seconds, beginning at 0
 */

#include "constants.h"
#include <cmath>
#include <iostream>
using namespace std;

double getPosition(double height, double time);

double* calculateResults(double height, int seconds)
{

    double positions[seconds + 1];
    for(int t = 0; t < seconds + 1; t++)
    {
        positions[t] = getPosition(height, t);
    }
    return positions;
}

double getPosition(double height, double time)
{
    double position = height - 0.5*constants::gravity*pow(static_cast<double>(time), 2);
    if( position < 0) position = 0;
    //Commented code is for testing
    //cout << position << endl;
    return position;
}

outputResults.cpp

/*
 *      Takes the array of results and prints them in an appropriate format
 */


#include <iostream>
#include <string>
#include <sstream>

using namespace std;

void outputResults(double* results){
    string outputText = "";
    //The commented code is to test the output method
    //Which is working
    //double results1[] = {1,2,3,4,5};
    //int numResults = sizeof(results1)/sizeof(results1[0]);
    int numResults = sizeof(results)/sizeof(results[0]);
    //cout << numResults; //= 0 ... Oh
    for(int t = 0; t < numResults; t++)
    {
        ostringstream line;
        line << "After " << t << " seconds the height of the object is " << results[t] << "\r";
        outputText.append(line.str());

    }
    cout << outputText;
}

And finally a couple of headers; dropSim.h

/*
 * dropSim.h
 */

#ifndef DROPSIM_H_
#define DROPSIM_H_

double getHeight();

double* calculateResults(double height, int seconds);

void outputResults(double* results);

#endif /* DROPSIM_H_ */

constants.h

/*
 *      Contains physical constants relevant to simulation.
 *      SI units
 */

#ifndef CONSTANTS_H_
#define CONSTANTS_H_

namespace constants
{
    const double gravity(9.81);
}

#endif /* CONSTANTS_H_ */
Community
  • 1
  • 1
Jekowl
  • 317
  • 2
  • 12
  • 3
    I'm voting to close this question as off-topic because this question belongs on [Code Review](http://codereview.stackexchange.com/). – TartanLlama Jun 04 '15 at 14:32
  • @TatanLlama Ah, I think your right... – Jekowl Jun 04 '15 at 14:38
  • 2
    Also, don't use `int numResults = sizeof(results)/sizeof(results[0]);` , that doesn't work with pointers. http://stackoverflow.com/a/4108340/1870760 – Hatted Rooster Jun 04 '15 at 14:39
  • @Jamey D thanks, good to know. – Jekowl Jun 04 '15 at 14:42
  • I think you have way overcomplicated the solution. It seems to me the question is asking for a simple `for()` loop with the relevant math. I don't see any reason to store each result in memory for longer than it takes to print it. – Galik Jun 04 '15 at 14:52
  • @Galik That would make sense here. It was a crude attempt to apply the SOLID principle; I was trying to separate the calculation and the output. – Jekowl Jun 04 '15 at 14:56

3 Answers3

6

I would say that you're over-engineering a big solution to a little problem, but to answer your specific question:

Should my program be structured differently, for performance, maintenance or style reasons, such that I would not be attempting to return an array like object?

Returning an array-like object is fine. But that doesn't mean returning an array, nor does it mean allocating raw memory with new.

And it's not restricted to return values either. When you're starting out with C++, it's probably best to just forget that it has built-in arrays at all. Most of the time, you should be using either std::vector or std::array (or another linear collection such as std::deque).

Built-in arrays should normally be viewed as a special-purpose item, included primarily for compatibility with C, not for everyday use.

It may, however, be worth considering writing your computation in the same style as the algorithms in the standard library. This would mean writing the code to receive an iterator to a destination, and writing its output to wherever that iterator designates.

I'd probably package the height and time together as a set of input parameters, and have a function that generates output based on those:

struct params {
    double height;
    int seconds;
};

template <class OutIt>
void calc_pos(params const &p, OutIt output) { 
    for (int i=0; i<p.seconds; i++) {
        *output = get_position(p.height, i);
        ++output;
    }
}

This works somewhat more clearly along with the rest of the standard library:

std::vector<double> results;

calc_pos(inputs, std::back_inserter(results));

You can go a few steps further if you like--the standard library has quite a bit to help with a great deal of this. Your calc_pos does little more than invoke another function repeatedly with successive values for the time. You could (for example) use std::iota to generate the successive times, then use std::transform to generate outputs:

std::vector<int> times(6);

std::iota(times.begin(), times.end(), 0);

std::vector<double> distances;

std::transform(times.begin(), times.end(), compute_distance);

This computes the distances as the distance dropped after a given period of time rather than the height above the ground, but given an initial height, computing the difference between the two is quite trivial:

double initial_height = 5;

std::vector<double> heights;

std::transform(distances.begin(), distances.end(), 
    std::back_inserter(heights),
    [=](double v) { return max(initial_height-v, 0); });

At least for now, this doesn't attempt to calculate the ball bouncing when it hits the ground--it just assumes the ball immediately stops when it hits the ground.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Thanks, this is a really useful answer. I notice you never use `using namespace std;` is that something that should be avoided? – Jekowl Jun 04 '15 at 15:05
  • 1
    @Jekowl: yes, I'd generally avoid it. The `using namespace foo;` statement can be useful in general, but the `std` namespace is large enough, and contains enough that I'd avoid making all those names directly visible--there's just too much chance of an accidental collision with something else you wrote. – Jerry Coffin Jun 04 '15 at 15:21
3

You should get rid of self-allocated double * and use std::vector<double> instead. It's not difficult to learn and a basic step in modern C++

marom
  • 5,064
  • 10
  • 14
  • I appreciate that this question doesn't belong here, but this definitely avoids answering it... – Jekowl Jun 04 '15 at 14:41
  • 1
    @Jekowl: Your current `calculateResults()` function returns a pointer to a local temporary array and simply doesn't work. Returning a `std::vector` would avoid that problem. – Blastfurnace Jun 04 '15 at 14:45
  • @Blastfurnace Thanks, that explains why marom's solution would fix the problem. The question was actually about whether my design was poor though. – Jekowl Jun 04 '15 at 14:51
1

This is how I would solve the problem:

#include <cmath>
#include <iostream>
#include <iomanip>

using std::cin;
using std::cout;
using std::endl;
using std::sqrt;
using std::fixed;
using std::setprecision;
using std::max;
using std::setw;

static const double g = 9.81;

class Calculator {
public:
   Calculator(double inh) : h(inh)
   {
   }

   void DoWork() const {
      double tmax = sqrt(h / ( g / 2));
      for (double t=0.0; t<tmax; t+=1.0) {
         GenerateOutput(t);
      }
      GenerateOutput(tmax);
   }
private:
   void GenerateOutput(double t) const {
      double x = g * t * t / 2;
      double hremaining = max(h - x, 0.0);     
      cout << fixed << setprecision(2) << setw(10) << t;
      cout << setw(10) << hremaining << endl;
   }

   double h;
};

int main() {
   double h(0.0);

   cout << "Enter height in meters: ";
   cin >> h;
   if (h > 0.0) {
      const Calculator calc(h);
      calc.DoWork();  
   } else {
      return 1;
   }

   return 0;
}
Mustafa Ozturk
  • 812
  • 4
  • 19