views:

1000

answers:

9

Let's say I have a struct defined as:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
} PhoneNumber;

When I create an instance of this struct, if I use the following syntax:

PhoneNumber homePhone = {858, 555, 1234};

...which constructor is it calling? The default constructor, or the copy constructor, or none at all because it's not calling 'new'?

The real purpose of this question is to figure out how I can add a fourth field. So I want to re-define my struct as:

typedef
struct number{
    int areaCode;
    int prefix;
    int suffix;
    int extension; // NEW FIELD INTRODUCED
} PhoneNumber;

So now, I can create new PhoneNumber objects with FOUR fields:

PhoneNumber officePhone = {858, 555, 6789, 777}

However, I have hundreds of these PhoneNumber instances already created with only 3 fields (xxx, xxx, xxxx). So I don't want to go through and modify EVERY single instantiation of my PhoneNumber object that is already defined. I want to be able to leave those alone, but still be able to create new phone number instances with FOUR fields. So I am trying to figure out how I can overwrite the constructor so that my existing three-parameter instantiations will not break, but it will also support my new four-parameter instantiations.

When I try to define an overriding default constructor that takes 3 fields and sets the fourth to a default value '0', I get errors (in the instantiation part of the code, not the constructor definition) complaining that my object must be initialized by constructor, not by {...}. So it seems that if I do override the default constructor, I can no longer use curly braces to create my new objects?

Sorry if this strays away from the original questions completely.

+1  A: 

It's effectively calling the default ctor; what's happening is a struct is allocated, and each value is assigned used default "=".

Charlie Martin
The default constructor is not called, and operator= is not called either. In C++ 8.5.1 it defines that syntax for initialization of aggregates.
David Rodríguez - dribeas
Did you *read* what I wrote? What is the default "="?
Charlie Martin
+1  A: 

A struct in C++ is like a class. The default constructor is being called. Afterwards, each field is copied with its assignment operator.

Uri
The default constructor is not called for POD types and the fields are not copied. The syntax defines the initialization of the members of the aggregate. See C++ 8.5.1
David Rodríguez - dribeas
A: 

none at all because it's not calling 'new'?

Just for the record, any object will always have a constructor called. You cannot use an object that has not been constructed.

GMan
Not exactly true... You can always cast a malloced pointer to the appropriate type.
Zifre
At the very least, C++ doesn't require the user to type "new" to call the constructor. Java and C# require "new" while C doesn't really have constructors (so malloc() doesn't call constructors).
Max Lybbert
I don't consider using malloc/free in C++ code :|
GMan
Not every object will have its constructor called always. POD types, for example will not have the implicit default constructor called.
David Rodríguez - dribeas
+5  A: 

The members are actually copy-initialized. The default constructor for each one is not called and no operator= is involved contrary to what some other answers suggest. It can be shown by a software called geordi - alternatively by reading through the Standard. I'll show the "fun" way using that software. It has got a class tracked::B that can show us when constructors/copy-constructors or destructors/copy assignment operators are called. The output it shows is (TRACK limits tracking to the statement following it):

B1*(B0) B1~

I used this code

struct T { tracked::B b; }; int main() { tracked::B b; TRACK T t = { b };  }

As you see, the second B object - which is the member within the local variable t, is copy initialized from the other object b. Of course, no assignment operator is activated. You can read about it in 12.6.1/2 in the Standard, if you wish.

The same, by the way, is true with arrays (which are likewise aggregates). Many people believe that objects that are member of arrays must have a type that has a default constructor. But that's not true. They can just be copy initialized by another object of their type and it will work fine.

All other elements that were not explicitly initialized in the aggregate are value initialized. Value-initialization is a mixture of default initialization and zero initialization. Actually, if a member has a type that has a user declared constructor, then that constructor is invoked. If it has a type that does not have a user declared constructor, then each member of it is value initialized. For built-in types (int, bool, pointers, ...) a value initialization is the same as zero initialization (that means that such a variable will become zero). The following will initialize each member to zero - except the first (a), which will be one:

struct T { int a, b, c; }; int main() { T t = { 1 }; }

Those initialization rules are scary, indeed - especially because the 2003 revision of C++ introduced that value initialization. It wasn't part of the Standard as of 1998. If you are more interested in those brace enclosed initializations, you can read How to initialize nested structures in C++?.

Johannes Schaub - litb
+2  A: 

It's not calling the default ctor, as others have written. Conceptually it's the same, but in practice, you'll find no function call in the assembly code.

Instead, the members remain uninitialized; you're initializing them with the curly-brace construct.

Interestingly enough, this:

PhoneNumber homePhone = {858, 555, 1234};

Results in this assembly (GCC 4.0.1, -O0):

movl  $858, -20(%ebp)
movl  $555, -16(%ebp)
movl  $1234, -12(%ebp)

Not many surprises there. The assembly is inline the function containing the above C++ statement. The values (starting with $) are moved (movl) into offsets into the stack (ebp register). They're negative because the memory locations for the struct members precede the initialization code.

If you don't fully initialize the struct, i.e. leave out some members like so:

PhoneNumber homePhone = {858, 555};

... then I get the following assembly code:

movl  $0, -20(%ebp)
movl  $0, -16(%ebp)
movl  $0, -12(%ebp)
movl  $858, -20(%ebp)
movl  $555, -16(%ebp)

Seems as if the compiler then actually does something very similar to calling the default constructor, followed by assignment. But again, this is inline in the calling function, not a function call.

If on the other hand you define a default constructor that initializes the members, to the given values, like so:

struct PhoneNumber {
  PhoneNumber()
    : areaCode(858)
    , prefix(555)
    , suffix(1234)
  {
  }

  int areaCode;
  int prefix;
  int suffix;
};

PhoneNumber homePhone;

Then you get assembly code that actually calls a function, and initializes the data members via a pointer to the struct:

movl  8(%ebp), %eax
movl  $858, (%eax)
movl  8(%ebp), %eax
movl  $555, 4(%eax)
movl  8(%ebp), %eax
movl  $1234, 8(%eax)

Each line that goes movl 8(%ebp), %eax sets the pointer value (eax register) to the beginning of the struct's data. In the other lines, eax is used directly, with an offset of 4 and an offset of 8, similar to the direct stack addressing in the previous two examples.

Of course all of this is specific to the compiler implementation, but I'd be surprised if other compilers did something extraordinarily different.

Does using the default constructor with the member initialization list generate a pointer dereference if optimization is cranked up? I.e. with gcc, does compiling with the -O3 option remove the pointer dereferences?
ceretullis
Looking at compiler output is no substitute for understanding the spec.
Don Neufeld
@ceretullis: I haven't looked. @don.neufeld: agreed, but that doesn't mean it's useless in understanding what's actually happening either.
A: 

However, I have hundreds of these PhoneNumber instances already created with only 3 fields (xxx, xxx, xxxx). So I don't want to go through and modify EVERY single instantiation of my PhoneNumber object that is already defined.

No problem, you just call update-instance-for-redefined-class and ..... er, nevermind. Proceed to mark this "unhelpful"

simon
+2  A: 

Initialization is, at least to me, one of the toughest parts of the C++ standard. The syntax you are using: Aggregate x = { 1, 2, 3, 4 }; defines the initialization of an aggregate type with the first four members assigned values 1, 2, 3 and 4.

As a note to your particular case (the struct has grown from 3 to 4 elements), the fields not present in the initialization list between the curly braces will be value initialized, which in particular for scalar types is equivalent to zero initialization which is itself the same as assigning 0. Thus your fourth element will be initialized to 0.

References

It is all defined in the C++ standard, chapter 8.5, and more precisely in 8.5.1 Aggregates. Aggregates will not be initialized with any implicitly declared default constructor. If you use the syntax above you are asking the compiler to initialize the given fields with the provided values. Any extra field in the aggregate shall be value initialized

Now, value initialization is defined as a call to the user defined default constructor if the type is a class with such constructor. If it is an array or a non-union class without user defined constructor then each one of the member attributes will be value initialized. Otherwise the object will be zero initialized, which then again is defined as...

Zero initialization is defined as setting the value to 0 for scalar types. For classes each class data member and base class will be zero initialized. For unions, the first member (only) will be zero initialized, for arrays all members will be zero initialized.

David Rodríguez - dribeas
A: 

There is no constructor on your type involved, so the existing three-parameter syntax would need to be modified at aggregate initialisation site.

Unless semantics of your newly added field ie. that new field type, are 'zeroed design-aware' ( idiom recommended by .NET programming, Brad and Co etc in design guidelines nonsense), you

cannot :

a) provide something more meaningful as either C++ default parameters if with some magic there was a method involved ( deault/optional params soon to be available in mass-market C# shop near the grocers web 4.0 and for old COM )

nor can you

b) follow the C# like pattern of designing value types with invalid value markers as 0s ( which is in this case arguably very bad and bad in general if you do not have control over the constants - something source-level libraries do very well and all-JavaTM MS -like frameworks suck at ).

In short, if 0 is a valid value you are stuffed and something most compiler writers saw as useful before any managed style or idiom ever existed; but it isn't necessarily right.

Anyway, you best chance isn't C++, or C#. It is code-generation, ie. more liberal meta-programming than modern C++ 'templating' pushes on.. You'll find it similar to JSON array annotation and could use either spirit or (my advice would be) roll your own utils. It helps in the long run, and eventually you'll want to get better at it (ie. what they call lame-man modelling aka Oslo in .NET).

[ problems like that are all over many languages, its a drawback of all C-style ones ( C, C++, Java, C#, you name it); very similar to arrays of arrays hackery required for any type of cross-domain or semantic I/O work and evident even in web-tech like SOAP etc.. new C++0x variadic bits don't help much here either but apparently can bring exponentially faster compile-times if you chose to play with some template hackery. Who cares :) ]

rama-jka toti