views:

547

answers:

12

I know my way around object-oriented programming, but I'm used to Java, and I never touched C++ until recently.

I think my problem is not so much related to syntax as to the philosophy of OOP in C++. I understand the difference between pointers and addresses and the stack and the heap, and stuff, but I still feel like I'm missing something.

Here's an example: I have a class (Shape) that holds some data. I have another class (App) using a number of Shapes.

class Square {
    private: 
        int x;
        int y;
        int size;

    public:
        /* constructor */
        Square(int x, int y, int size);
}


class App {
    private:
        Square redSquare;
        Square blueSquare;

    public:
        void setup();
        void draw();
}

At some point something is going to instantiate my App and call setup(). The problem is that when I declare the App class (in App.hpp, say) the "redSquare" and "blueSquare" get instantiated, not just declared. Being a Java programmer, I would in this example instantiate my classes in setup(). But thatmeans I can't do it as above, I'll have to set up redSquare and blueSquare as POINTERS, then I can create them using new in setup().

But is that how you would do it? Or would you make a constructor with default parameters, create the redSquare and blueSquare as above, and then set the values of those squares in App.setup(), using something like a Square.init(x, y, size) or something? Or some other way?

Do you ever aggregate classes, or only pointers?

I can certainly hack this one way or the other so it works myself, but I have a feeling I'm doing things "the Java way" and not getting how C++ programmers think.

+2  A: 

You need to specify the parameters to your constructor in your App constructor, e.g.

App(int bx, int by, bsize, int rx, int ry, int rsize) : 
blueSquare(bx, by, bsize), redSquare(rx, ry, rsize) {}

It's just an example, you'll probably use a better design IRL.

Edouard A.
Of course! I was thinking of the initialization list as only a way to pass things on to superclasses and PODs, but you can pass parameters to classes as well. I still feel like I'm doing something C++ coders would frown at, but perhaps I'm not...Thanks!
The design is indeed suboptimal, but that's not a C++ problem.
Edouard A.
I encourage you to have a look at boost::shared_ptr, if you come from the Java world you will love them :)
Edouard A.
+2  A: 

Give app a constructor:

App :: App() 
    : redSquare( 10, 20, 100 ), blueSquare( 50, 500, 25 ) {
}
anon
A: 

The Square class has a constructor that takes 3 arguments. The App class doesn't define a constructor - that implies that the compiler will supply a default constructor (the constructor that does not take any parameter). In a default constructor of a class, all member variables are initialized by default constructor. Since your Square class does not have a default constructor, your code will not compile.

A constructor should initialize a class instance with default values to its member variables. If you need to change any of these variables, then you can define some getter and setters:

class Square {
    private: 
        int x;
        int y;
        int size;

    public:
        /* constructor */
        Square(int x, int y, int size);

        int getX() const {return x;}
        int getY() const {return y;}
        int getSize() const {return size;}

        void setX(int nx) {x = nx;}
        void setY(int ny) {y = ny;}
        void setSize(int s) {size = s;}
}

Then call these functions to change internal variables of Square.

Donotalo
A: 

I would use Square * instead of Square as private members of App. That way memory is saved, the construction of the Square objects in App can be delayed to whenever needed, or they can be done in the constructors of App, default or otherwise. Of course, the destructor should destroy the two Square objects if they were created.

alok
Yes, this is what I did as well, but I was somehow feeling that maybe I was doing something odd.
If the Square class is huge and it doesn't need to instantiated immediately, it would be a good idea to delay the constructor calls.
alok
A: 

Either use pointers, or make the App constructor take the parameters as suggested above.

(in App.h)
Square* redSquare;
Square* blueSquare;
void setup(int,int,int,int);
...

(in App.cpp)
void App::setup(int x1,int y1,int x2,int y2){
    redSquare=new Square(x1,y1);
    blueSquare=new Square(x2,y2);   // don't forget to "delete" these later
    ...
}
A: 

You should use pointers instead of actual objects.

Using pointers will make your class behave similarly to Java (it will force you to explicitly instantiate it on "setup" method or constructor). In Java, every object is a reference (similar {but not equal} to a pointer) and there is no equivalent to what you accomplish with C++ by using:

class A {
   B b;
}

Hope it helps.

Pablo Santa Cruz
This actually bad advice. As the saying goes: When in Rome, do as the Romans do. There's no point in using C++ in a way that it resembles Java, stay with Java if that's the goal.
mghie
using pointers for objects is NOT a bad thing in C++! +1
hasen j
having said that, you still need to beware of memory leaks
hasen j
+2  A: 

First, you need to choose how you want your App class to behave if draw() is called before setup():

  1. If you want to have some predefined squares by default, then initialize them in the App initialization list.

  2. If you don't want this to ever happen, then use pointers and protect against this.

Some notes on 2:

  • You don't have a garbage collector in C/C++, you have to 'delete' what you 'new'
  • You can use "smart pointers" to have RAII (Resource Allocation Is Initialization), which will make memory management simpler
  • You should probably use the Null Design Pattern to avoid many 'if ( redSquare == NULL )' blocks.
  • In any case, initialize the pointers in App's initialization list, either to NULL or a null square object.
Gilad Naor
A: 

I'll let the code talk. Caveat: Untested code!

/**
 Typically we will use Shape as a base class.
 This defines the interface that clients of the
 Shape class can expect and use reliably. Of
 course, some shapes can be dough-nuts and have 
 a eat() method, but that's just them dough-nuts.
*/
class Shape
{
public:
 /**
  The base class's default ctor.
  The compiler generates one if you don't specify any ctors,
  but if you specify at least one, this is skipped, so we 
  to mention it explicitly
 */
 Shape(void) {} 
 Shape(const std::string& c) { std::cout << c.length() << std::endl; } /* print the color */
 ~Shape(void) {}

 /**
  The interface. The '=0' makes it virtual. This class
  has now become abstract. Subclasses MUST define this
  member function.
 */
 virtual size_t area() = 0 { return 0; }
};

/**
 The more interesting objects, real world shapes.
 They have their own version of implementing an
 algorithm to find out area.
*/
class Circle : public Shape
{
public:
 Circle(const std::string& color) /* no default parameters */
   /* the base class's default ctor is called automagically */
  :_color(color) /* the user must specify the color */  
 {}
 ~Circle(void) {}

 virtual size_t area() /* = 0 */ /*we dont want this to be abstract! */
            { return 42; }

private:
 std::string _color;
};

class Triangle: public Shape
{
public:
 Triangle(const std::string& color = "red") 
  Shape(color)/* this base class ctor has to be called by you */
  : _color(color) /* use initializer lists to populate ctor parameters */
 {}
 ~Triangle(void) {}

 virtual size_t area() { return 42 + sizeof Triangle; }
private:
 std::string _color;
};

/**
 We will also use design patterns. And an oft-abused 
 one -- the Singleton. We want only one app to run after
 all.
*/
class App {
public:
 /**
  This would typically be in a differnt '.cpp' file.
 */
 int Run() {
  /* create a few shapes */
  Triangle t1;
  Circle c1; // error -- no default parameter
  Circle c2("green");

  /* You want to sum the areas of all these objects */
  size_t total_area = t1.area() + c2.area();

  /* Now, how do you process different shapes in one go? */
  /* Put them in a container for base class pointers */
  std::list<Shape *> shape_list;
  shape_list.insert(&t1); 
  shape_list.insert(&c2);

  /* process them -- calculate total area differently */
  size_t total_area2 = 0;
  std::list<Shape *>::iterator f = shape_list.end();
  for (std::list<Shape *>::iterator i = shape_list.begin();
   i != f;
   ++i) {
   total_area2 += i->area(); 
   /* see why we need virtual functions? */
  }

  /* or use the STL to the hilt and do */
  size_t total_area3 = std::accumulate(shape_list.begin(), shape_list.end(), 
   0, 
   std::bind2nd(std::plus<size_t>(std::mem_fun(&Shape::area))));


 }
private:
 App();
 App(const App&);
 App& operator(const App&);
};

/* the program entry-point, you have to have one, and only one */
int main() {
 App.Run();
}
dirkgently
+6  A: 

It seems to me that the problem comes from the presence of the "setup()" method. IIUC, the intent is to use App as:

App a;
a.setup();

In what state is "a" before the call the setup()? What should happen if draw() is called on it? If the answer is something like "setup() must be called before anything maningful could be done with an App object", then it means that setup() is the "real" constructor of App, and that App's current ctor is meaningless. Conclusion: Remove the "setup()" method and replace it by a meaningful ctor.

Éric Malenfant
Your question about the state before "setup()" is key. But if the object is intended to be reused -- by calling "setup()" before each use -- then this separation would make sense. In that case I'd suggest calling "setup()" within the ctor instead of moving the code.
NVRAM
+1  A: 

Why are you using a setup/init function ? Use a constructor instead, initializing objects are constructor's raison d'être. Objects that may exist in unusable states are PItA, they require each function to test whether we can do anything good with them. Strengthen their invariant and forbid their existence until you are able to fully construct them.

Even in Java, init/setup functions appear to me as a questionable practice.

Luc Hermitte
A: 

If your setup() truly has no parameters and doesn't return any values then put that functionality in the constructor (I assume you just left those out for brevity).

Having a setup() function that must be called after your constructor is not best, but may be perfectly valid when throwing an exception in the constructor is not an option. Your setup() function could handle problems in a more flexible fashion.

HOWEVER: do not let a constructor leave the object in a state that will cause problems later, specifically:

  • Initialize all pointer member variables!! In general you should set everything to some meaningful defaults. At an absolute minimum have a guard bit to determine if setup() has been done and handle every case where it may bite you.
  • In every function that needs setup() to have been called, ensure that it has, or call it then.
  • And, of course, free all allocations in the destructor!

To put it another way: Member variable initialization is like cleaning your house before your Mother-in-Law visits -- either do it immediately or put a guard in place to allow you do it before it's required.

Oh, and don't waste much time trying to avoid pointers in C++, but do make sure you learn how to avoid the pitfalls.

NVRAM
A: 

There is NO C++ish way to do this.

The C++ way is:

Do what you want, I'll give you as much rope as you want.
Hell man! you could even hang yourself with it, I'm not gonna stop you!

You could use pointers, but you'll have to be aware of memory leaks! You have to define who (as in which class/object) owns this object, who has the exclusive right to delete it!

OR, you could plugin a garbage collector (I've heard there are some popular ones, but never tried any myself), and then work with pointers as if you were in java.

hasen j
OR, you could use local objects with value semantics, and simply reassign them as necessary. That's probably better than using dynamic allocation in this case, since the sub-objects are so small and trivial.
Tom