Skip to main content
GameDev.net gamedev.net
🔒 Locked

named subtypes of a class

Started by Storyyeller Nov 21, 2009 at 12:16 PM 6 replies 1k views
Original Post
Storyyeller
Storyyeller
Is there any simple way to make a class that is really another class with different constructor arguments? I guess I'ts hard to explain. Suppose I have a class Rectangle with the constructor Rectangle(double x, double y, double w, double h) And I want to do something like Rectangle myrect = Square(0,0,50); I could use inheritance like this

class Square: public Rectangle
{
   public:
   Square(double x, double y, double side): Rectangle(x,y,side,side){}
}

But it seems like such a waste to create a separate class like this when all I want to do is call the constructor with some arguments pre filled in basically. And then I have to worry about adding virtual destructors etc. Is there anyway to do this without making a seperate Square class? I guess macros are a possibility, but they are really easy to mess up and make the code difficult to understand. Or am I overthinking things?
I trust exceptions about as far as I can throw them.
Brother Bob
Brother Bob
Do you mean something like this?
class Rectangle{    Rectangle(double x, double y, double sidex, double sidey) : x(x), y(y), sidex(sidex), sidey(sidey) {}    Rectangle(double x, double y, double side)                : x(x), y(y), sidex(side),  sidey(side)  {}    double x, y, sidex, sidey;};

Or you can make a function that creates a rectangle with square properties and return it.
Rectangle Square(double x, double y, double side){    return Rectangle(x, y, side, side);}

And drop the three-parameter constructor.
Storyyeller
Storyyeller
The thing I was wondering about with the named constructor and function approaches is that it seems like you would have to make a separate function for static and dynamic allocation each time.

Also, adding other constructors would tend to clutter up the base class header with stuff that are not logically part of it.

I guess there are no perfect solutions and I'll just have to pick the one that seems the least bad.
I trust exceptions about as far as I can throw them.
Antheus
Antheus
This is fundamental problem OO. Inheritance is poorly applied to this type of relation.

Squareness is a property of a rectangle. If anything, they are both special forms of polygon.

While this type of relations can be modeled using inheritance, the obvious problems soon creep up. This is why it is important to determine what this type of classes are to be used for. Rigid inheritance based definitions are sometimes used in graphics packages for various performance reasons, but tend to get into way for anything beyond that.


In most cases, I would organize my types like this:
struct Polygon {  int pointCount();  Point getPoint(int i);  // these would probably be inherited from some Geometric or   // Euclidian base class as virtual methods  void translate(Point & delta);  void scaleAround(Point & center, double factor);  ...};...bool isRectangle(Polygon & p) {  return p.pointCount() == 4 && hasParallelSides(p) && areAngles90Degrees(p);}Polygon makeSquare(int x, int y, int w) {  ... // make 4 points, etc...}


If performance were important, but not critical to enough very rigid typing, I would solve the cost of queries above using flags. I would add Polygon class an enum or a bitfield, which would use bit flags to set properties of a polygon, such as IsRectangle, IsSquare, IsConvex, ...

Note that curves cannot be accurately represented using a polygon, which is a much more natural distinction. For example, circle and ellipse are not polygons, although they can be approximated by one. For those I would provide a more useful interface. Perhaps something like:
struct Curve {  void iterate(PointVisitor & visitor, int nDivisions);};
This way, any curve and be implicitly and accurately represented, yet exposes a single practical interface.


Just a slightly different perspective on topic.
Storyyeller
Storyyeller
The rectangle/square thing was just an example.

My code really looks more like this

class BlackChomper: public DeadlyBrush{    Uint32 TICKSPERFRAME() const {return (Uint32) (240.0 / MSPERFRAME);}    public:    BlackChomper(double initialx, double initialy): DeadlyBrush( initialx, initialy, 32, 30,        new BasicRowAnimation(zlENTITY, ImgDataStruct("KaizoPickory/BlackChomper.bmp",0,255,0), TICKSPERFRAME(), 0, 32, 30, 2))        {}};
I trust exceptions about as far as I can throw them.
Antheus
Antheus
Two ways are commonly used to solve this problem.

One is prototype objects:
class DeadlyBrush {  DeadlyBrush makeBlackChomper();}
For the same reason as mentioned previously - are BlackChomper and DeadlyBrush really that different to warrant separate classes?

For example, we are all humans, each individual is just an instance defined by reconfiguration of common DNA. So a Human is a Human. While child inherits the DNA, Child does not extend Parent. The inheritance applies to values, they keep some, change the others, perhaps add some (harder in C++, really trivial in &#106avascript).


Alternative to that is to pass configuration to class, and let it make out what it wants:
class Foo {  Foo(Properties & p) {    this.x = p.get<int>("x", 0);    this.y = p.get<int>("y", 0);    this.name = p.get<string>("name", "default name");  }};
Here, common configuration class (perhaps loaded from file or database) provides values for shared or common instance.

Properties can hold values that are not of use to instance being created. It may also lack certain properties which class being created needs, so the defaults are provided. The above reads something like this: set name to property with key "name", if there is no such property, set name to "default_name".


The above approaches may seem like an overkill, they are just commonly found in practice whenever flexibility is required.
Storyyeller
Storyyeller
In this case I realized that it there was no point to making BlackChomper a subclass of DeadlyBrush. Since DeadlyBrush turns out to itself have no methods or member variables, only a couple lines of code needed to be duplicated, a much simpler task then inheriting for no reason.

class BlackChomper: public Entity{    Uint32 TICKSPERFRAME() const {return (Uint32) (240.0 / MSPERFRAME);}    public:    BlackChomper(double initialx, double initialy): Entity(initialx,initialy)    {        SetAttribute(AttributeList::DEADLY,true);        MyShape().setRect(0,0, 32, 30);        SetAnimation(new BasicRowAnimation(zlENTITY, ImgDataStruct("KaizoPickory/BlackChomper.bmp",0,255,0), TICKSPERFRAME(), 0, 32, 30, 2));    }};



Anyway, the reading definitions from a file thing looks interesting, but I think I have a long way to go before I'll try tackling stuff like that.
I trust exceptions about as far as I can throw them.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.