Advertisement

How to initialize a class member variable when using compositon?

Started by March 22, 2015 09:04 AM
15 comments, last by WozNZ 9 years, 10 months ago

I am struggling to understand a concept when using composition in c++. I have created a demo program to illustrate my confusion. The program might seem a bit pointless but its only purpose is to illustrate my question.


#include <iostream>
using namespace std;

class Shape
{
private:
    double width;
    double length;
    double area;

public:
    void init(int, int);
    double getArea() const
    {
        return area;
    }

};

class Rectangle : public Shape
{
private:
    
public:

};

void Shape::init(int wid, int hei)
{
    width = wid;
    length = hei;

    area = length * width;
}

class ShapeArea
{
private:
    Rectangle rectShape;
    int area;

public:
    ShapeArea()
    {
        area = rectShape.getArea();
    }
};

int main()
{
    Rectangle rectShape;
    rectShape.init(5, 5);
    
    ShapeArea newshape;

    getchar();
    return 0;
}

In this program I create a rectShape object, and initialize its width and length as 5, and the program calculates its area, and stores them all as member variables.

Now I have created a class called ShapeArea, and using composition given it Rectangle object called rectShape.


class ShapeArea
{
private:
    Rectangle rectShape;
    int area;

public:
    ShapeArea()
    {
        area = rectShape.getArea();
    }
};

In its constructor, I am allowed to call rectShape.getArea(), because getArea() is a member function of class Rectangle (derived from class Shape). The problem I am facing is that the rectShape object it is using to call this function is filled with random values in memory.

So my question is, in this design, how do I tell class ShapeArea that the rectShape Rectangle object I want to call the getArea() function of, is the one I have already created earlier in the program? Ideally I want the newshape object I create to have the value "25" as area.

I hope this question is clear.

Thanks

Do you mean something like this?

class ShapeArea
{
private:
    Rectangle rectShape;
    int area;

public:
    ShapeArea(const Rectshape &rs) : rectShape(rs), area(rs.getArea())
    {
    }
};
 
 
ShapeArea n(myrectshape);
Advertisement
What braindigitalis said is what I believe you want. But honestly your code is really convoluted.

This would do the same thing essentially:

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape(double width, double length) : m_width(width), m_length(length)
    {
    
    }
    ~Shape() = default;
	
    double Area() const
    {
        return width * length;
    }
private:
    double m_width;
    double m_length;
};

int main()
{
    Shape rectShape(5.0, 5.0);
	
    // Do stuff with rectShape...
    double area = rectShape.Area();

    getchar();
    return 0;
}

filled with random values in memory

It's the job of the constructor to initialize the object to avoid this.
Constructors are init functions. No need to make extra init functions as well...

how do I tell class ShapeArea that the rectShape Rectangle object I want to call the getArea() function of, is the one I have already created earlier in the program

Pass a reference to the Rectangle that you want to inspect as an argument of that function.

Do you mean something like this?



class ShapeArea
{
private:
    Rectangle rectShape;
    int area;

public:
    ShapeArea(const Rectshape &rs) : rectShape(rs), area(rs.getArea())
    {
    }
};
 
 
ShapeArea n(myrectshape);

Just to emphasize something in braindigitalis' post - note that he asked the parameter for the area, and didn't ask his class member for the area in the constructor initializer list. Constructor initializer lists are executed in the order the variables are defined in the class - not the order they are listed in the initializer list. Fortunately most compilers will warn now if the order doesn't match. But this could cause problems if, for example, you asked your class member for the area instead of the parameter.

Constructor initializer lists are executed in the order the variables are defined in the class - not the order they are listed in the initializer list.

Yes, this is exactly why I always do it this way. It's easy to forget this strange behaviour of c++ but easy to remember to always use the parameter, not the member :)

Also it's worth noting that an initializer list can initialise a const member. There is no other point where you can set a const member and of course once set they can't be changed, e.g.

class foo {
  const int bar;
  foo (int a) : bar(a) { }
};
Advertisement
When in doubt, follow the example of the language's standard library. It is well designed and there is much to learn.

First, they initialize everything in their constructor. The fill in all the value that are necessary to initialize it.


What do they initialize it to? Generally they are initialized to an EMPTY state.

Create a string and you get an empty string. Create a vector, a map, a list, and you get an empty container. Create an fstream and you get an empty object not pointing to any file but still ready for use. You may provide additional constructors that do work, but those are not the default.

A common problem, especially here in For Beginners, is the thought that to "initialize" an object means reading from disk, parsing data, allocating resources, and doing other expensive operations. While you can provide alternate functions and constructors for that, you should have your default constructor initialize your objects to a clean, empty, usable state.

Finally, for the beginners, the concept of initializing with a constructor is an improvement from what was common back in the 1970s and earlier. First you would allocate the memory, and it would be 'random' data. The first instruction after a successful allocation was almost universally one or more lines of initialization. Usually that was a memset to zero, other times it was a memset to zero followed by setting a few individual actions away from zero. The constructor provides a nice way to do this without the two-step dance of allocate and initialize to zero.
Just to play devil's advocate:

There _are_ reasons to avoid use of a constructor in _some_ situations. No constructor (and no Non-Static Data Member Initializers) means that your class is trivially constructible, which is an important property for being able to treat it as Plain Old Data and being allowed to memcpy and memset it freely.

There are also cases with objects pools and expensive-to-construct objects.

Sean Middleditch – Game Systems Engineer – Join my team!

When in doubt, follow the example of the language's standard library. It is well designed and there is much to learn.

First, they initialize everything in their constructor. The fill in all the value that are necessary to initialize it.


What do they initialize it to? Generally they are initialized to an EMPTY state.

Create a string and you get an empty string. Create a vector, a map, a list, and you get an empty container. Create an fstream and you get an empty object not pointing to any file but still ready for use. You may provide additional constructors that do work, but those are not the default.

A common problem, especially here in For Beginners, is the thought that to "initialize" an object means reading from disk, parsing data, allocating resources, and doing other expensive operations. While you can provide alternate functions and constructors for that, you should have your default constructor initialize your objects to a clean, empty, usable state.

Finally, for the beginners, the concept of initializing with a constructor is an improvement from what was common back in the 1970s and earlier. First you would allocate the memory, and it would be 'random' data. The first instruction after a successful allocation was almost universally one or more lines of initialization. Usually that was a memset to zero, other times it was a memset to zero followed by setting a few individual actions away from zero. The constructor provides a nice way to do this without the two-step dance of allocate and initialize to zero.

This.

Actually if there is one rule when it comes to constructors(okay, programming in general) it is that everything is situational.

The only real job of a constructor is to initialize the object to sensible values that represent at least an EMPTY state, how much work you want to do in the constructor is completely situational and depends on a lot of factors.

Personally I prefer to do as little work as I can in the constructor unless it is cheap and simple and has little chance of needing to throw. Some people prefer the opposite. I just always found it a little offputting to think that if someone creates an instance of an object of mine then it would do a lot of heavy lifting and computation as a literal side effect of being constructed, rather than it happening when you explicitly ask for it(initialize functions.)

The important part is not to try and force some amazing consistancy between all your objects, the important part is to give them all a simple to understand interface.


I just always found it a little offputting to think that if someone creates an instance of an object of mine then it would do a lot of heavy lifting and computation as a literal side effect of being constructed, rather than it happening when you explicitly ask for it(initialize functions.)

The important part is not to try and force some amazing consistancy between all your objects, the important part is to give them all a simple to understand interface.

I disagree, the important part is to clearly document your constructors with doxygen or similar style comments, so that a programmer using the class can clearly see what it will do when constructed. Sometimes heavyweight classes which do a lot on construction make sense (e.g. A class which represents the entire game) and other times since fast initialization matters, e.g. when initialising a class representing a vertex. Also, I have no aversion to using exceptions if clearly documented. Exceptions are perfect for throwing errors from a constructor.

As previously stated, highly situational but documentation helps.

This topic is closed to new replies.

Advertisement