C++ constructor question

197 views Asked by At

In the C++ programming for the absolute Beginner, 2nd edition book, there was the following statement:

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

Is this equal to:

HeapPoint::HeapPoint(int x, int y) { thePoint = new Point(x,y); }

And, since we are doing this in a constructor, what are the values assigned for x and y? Should we write values insted of x and y in new Point(x,y)? Or, it is correct that way?

UPDATE: I think I got the idea of initializing x and y, as in the book it has the following in a function:

HeapPoint myHeapPoint(2,4);
6

There are 6 answers

0
Marcelo Cantos On

Assuming thePoint is a raw pointer, it is, for all intents and purposes, the same. The first version initialises thePoint, whereas the second assigns to it, but the effect is almost always the same, even to the point of generating exactly the same machine code.

When aren't they the same?

  1. If thePoint is some kind of smart pointer, the first form will initialise it by passing the new Point to its constructor, whereas the second form will probably zero-initialise it and then pass the new Point to its assignment operator.
  2. When thePoint is one of several member variables, the first form will initialise it in the order it appears in the class definition, whereas the second form will assign in the body of the constructor. So the order in which things get "hydrated" may vary between the two forms, which may matter if some members variables depend on others.

If you are a beginner, most of this is probably meaningless to you. But don't worry about it. These differences are very subtle and will almost never matter. (I'm sure there are other exceptions, but none come to mind just now.)

It's ultimately a matter of style and consistency, for which I prefer the initialisation form over the assignment form.

0
justin On

the first is not the same as the second.

in this specific case, they will probably produce the same result. however, Point could easily implement an assignment operator for new Point and do something 'different' (i don't have the book, so i don't know every detail). as well, the assignment operator should do what you'd expect... however, thePoint could be a container (e.g., smart pointer) which could (for some odd reason) behave differently when using initialization(Point) vs default initialization followed by assignment.

these details likely won't matter in this case, but they do affect initialization order, and the execution. the difference will be important when your programs grow. at that time, initialization will take time, and you will want to ensure that the objects are initialized correctly: that they are constructed properly (the first time) and that they are constructed in the right order. most obvious case: it will make a difference when a default constructor behaves different from one with parameters, especially when the constructor produces allocations or has other time-consuming (or behaviorally different) side effects.

And, since we are doing this in a constructor, what are the values assigned for int x and int y?

that depends entirely on Point's constructor.

Should we write values insted of x and y in new Point(x,y)? Or, it is correct that way?

the preferred way (for most teams) is to use the initialization list and formal constructors wherever possible, and to write your types to support correct initialization. there are a lot of subtleties that come out when codebases grow. this constructor uses the initialization list:

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

proper initialization could in a hypothetical case be required if you want to declare thePoint like so:

const Point* const thePoint;

the first const means that you cannot modify the Point (e.g., Point.x or Point.y). the second const means that you cannot assign a new allocation to the variable. trivial examples for the example in the OP, but definitely helpful as your programs grow.

2
CashCow On

Generally you should prefer the first construct, i.e. using the initialiser list.

The second construct is preferable if

  1. you wish to put a try..catch around it or
  2. You have several of these and are going to store them as regular pointers, in which case you need to beware that one of your news might fail and you will need to do some cleanup. You would handle this by using some kind of auto_ptr / unique_ptr until you know all the allocations have succeeded, then go around releasing them. This is because you presumably delete them in your destructor, but your destructor will not get called if the constructor throws.
1
ajmartin On

Both are same. The first case:

HeapPoint::HeapPoint(int x, int y): thePoint(new Point(x,y)) { }

uses the constructor of the class to which thePoint belongs, and makes it point to a new allocated memory for Point.

The second case also assigns the memory, and its address is assigned to thePoint

0
Tony Delroy On

The two forms aren't quite the same in that the initialisation list gives a value to be placed directly into the variable, whereas the variables are otherwise default-initialised if appropriate (e.g. std::strings are empty by default, but int, double etc. member variables have effectively random values), then overwritten when the assignment operation is encountered. This overwriting can be less efficient, but it can also be illegal for some constant and reference member variables that aren't allowed to be changed after they're created. In general, use the initialiser list whenever possible and you won't go far wrong. Only when you find you can't initialise something yet because you need to do some other steps first should you put an explicit assignment statement inside the constructor body. Be aware the initialisations take place in the order in which the member variables are declared in the class, irrespective of the order you list them in the constructor itself: a good compiler (e.g. GCC) can warn you about this.

1
Emmanuel On

In your 1st case, you use an initialization list to setup your member thePoint, whereas in your 2nd example you use the body of the constructor. The work done behind is not the same, since initialization lists are used before your object is built, and once this is finished your constructor body is used.

So:

  • in your 1st case, your member thePoint is directly built with thePoint(new Point(x,y))

  • in the 2nd case it is first allocated probably because a default constructor is available for it, then the object built is overridden with the call made in the body (yes, the same one, but not at the same place) !! So you lost efficiency here.

If initialization lists exist, this is for good reasons (C++ is a very precise language, the syntax is really strict, except for ugly things coming from C all is logical) ! For instance if your class used a reference member, you'd be obliged to use an initialization list, since your object would not be entirely built:

class A
{
public:
    B& _b;
    C& _c;

    A(B& b, C& c):_b(b), _c(c) // compiles !
    {

    }

    A(B& b, C& c)
    {
        _b(b); // does not compile !
        _c(c); // does not compile !
    }
}

Keep in mind that here, if we had done _c(c), _b(b) in the 1st contructor (reverse order), copy constructors of classes B and C would be called in the same order anyway: they are called in the order the members are defined (i.e. _b before _c), not the order you write them !!!!