How to write a Java-enum-like class with multiple data fields in C++?
Coming from a Java background, I find C++'s enums very lame. I wanted to know how to write Java-like enums (the ones in which the enum values are objects, and can have attributes and methods) in C++.
For example, translate the following Java code (a part of it, sufficient to demonstrate the technique) to C++ :
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Planet <earth_weight>");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight/EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Your weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}
Any help would be greatly appreciated!
Thanks!
One way to simulate Java enums is to create a class with a private constructor that instantiates copies of itself as static variables:
class Planet {
public:
// Enum value DECLARATIONS - they are defined later
static const Planet MERCURY;
static const Planet VENUS;
// ...
private:
double mass; // in kilograms
double radius; // in meters
private:
Planet(double mass, double radius) {
this->mass = mass;
this->radius = radius;
}
public:
// Properties and methods go here
};
// Enum value DEFINITIONS
// The initialization occurs in the scope of the class,
// so the private Planet constructor can be used.
const Planet Planet::MERCURY = Planet(3.303e+23, 2.4397e6);
const Planet Planet::VENUS = Planet(4.869e+24, 6.0518e6);
// ...
Then you can use the enums like this:
double gravityOnMercury = Planet::MERCURY.SurfaceGravity();
With C++11's introduction of constexpr
. There's yet another way to implement typed enums. One that works practically the same as normal enums (is stored as an int
variable and can be used in a switch
statement), but also allows them to have member functions.
In the header file you would put:
class Planet {
int index;
public:
static constexpr int length() {return 8;}
Planet() : index(0) {}
constexpr explicit Planet(int index) : index(index) {}
constexpr operator int() const { return index; }
double mass() const;
double radius() const;
double surfaceGravity() const;
};
constexpr Planet PLANET_MERCURY(0);
constexpr Planet PLANET_VENUS(1);
constexpr Planet PLANET_EARTH(2);
// etc.
And in the source file:
static double G = 6.67300E-11;
double Planet::mass() {
switch(index) {
case PLANET_MERCURY: return 3.303e+23;
case PLANET_VENUS: return 4.869e+24;
case PLANET_EARTH: return 5.976e+24;
// Etc.
}
}
double Planet::radius() {
// Similar to mass.
}
double Planet::surfaceGravity() {
return G * mass() / (radius() * radius());
}
Which can then be used as:
double gravityOnMercury = PLANET_MERCURY.SurfaceGravity();
Unfortunately, the enum entries cannot be defined as static constants within the class body. They must be initialized upon declaration, because they are constexpr
, but inside the class, the class is not yet a complete type and thus cannot be instantiated.
May be this is what you want --
#include<iostream>
using namespace std;
class Planet {
double mass,radius;
Planet(double m, double r) : mass(m) : radius(r) {}
public:
static const Planet MERCURY;
void show(){
cout<<mass<<","<<radius<<endl;
}
} ;
const Planet Planet::MERCURY = Planet(1.0,1.2);
int main(){
Planet p = Planet::MERCURY;
p.show();
}
This is just a small code, Im sure you can modify this to suit your needs..
This is ugly, verbose, and generally a dumb way to go. But I figured I'd post a complete code example by way of explanation. For extra points it's actually possible to define a compile-time expanded iteration over the solar planets by tweaking the template specializations just a tad.
#include <string>
#include <sstream>
#include <iostream>
#include <cstdlib>
class Planet {
public:
static const double G = 6.67300E-11;
Planet(const ::std::string &name, double mass, double radius)
: name_(name), mass_(mass), radius_(radius)
{}
const ::std::string &name() const { return name_; }
double surfaceGravity() const {
return G * mass_ / (radius_ * radius_);
}
double surfaceWeight(double otherMass) const {
return otherMass * surfaceGravity();
}
private:
const ::std::string name_;
const double mass_;
const double radius_;
};
enum SolarPlanets {
MERCURY,
VENUS,
EARTH,
MARS,
JUPITER,
SATURN,
URANUS,
NEPTUNE
};
template <SolarPlanets planet>
class SolarPlanet : public Planet {
};
template <>
class SolarPlanet<MERCURY> : public Planet {
public:
SolarPlanet() : Planet("MERCURY", 3.303e+23, 2.4397e6) {}
};
template <>
class SolarPlanet<VENUS> : public Planet {
public:
SolarPlanet() : Planet("VENUS", 4.869e+24, 6.0518e6) {}
};
template <>
class SolarPlanet<EARTH> : public Planet {
public:
SolarPlanet() : Planet("EARTH", 5.976e+24, 6.37814e6) {}
};
template <>
class SolarPlanet<MARS> : public Planet {
public:
SolarPlanet() : Planet("MARS", 6.421e+23, 3.3972e6) {}
};
template <>
class SolarPlanet<JUPITER> : public Planet {
public:
SolarPlanet() : Planet("JUPITER", 1.9e+27, 7.1492e7 ) {}
};
template <>
class SolarPlanet<SATURN> : public Planet {
public:
SolarPlanet() : Planet("SATURN", 5.688e+26, 6.0268e7) {}
};
template <>
class SolarPlanet<URANUS> : public Planet {
public:
SolarPlanet() : Planet("URANUS", 8.686e+25, 2.5559e7) {}
};
template <>
class SolarPlanet<NEPTUNE> : public Planet {
public:
SolarPlanet() : Planet("NEPTUNE", 1.024e+26, 2.4746e7) {}
};
void printTerranWeightOnPlanet(
::std::ostream &os, double terran_mass, const Planet &p
)
{
const double mass = terran_mass / SolarPlanet<EARTH>().surfaceGravity();
os << "Your weight on " << p.name() << " is " << p.surfaceWeight(mass) << '\n';
}
int main(int argc, const char *argv[])
{
if (argc != 2) {
::std::cerr << "Usage: " << argv[0] << " <earth_weight>\n";
return 1;
}
const double earthweight = ::std::atof(argv[1]);
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<MERCURY>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<VENUS>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<EARTH>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<MARS>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<JUPITER>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<SATURN>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<URANUS>());
printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<NEPTUNE>());
return 0;
}