Intialization withn definition?

Started by
7 comments, last by SeanMiddleditch 9 years, 9 months ago

  struct Tile
  {
    TileType      Type;
    unsigned int  Color;
    unsigned int  Flags;

    Tile( TileType Type = TILE_EMPTY, unsigned int Color = 0xffffffff, unsigned int Flags = 0 ) :
      Type( Type ),
      Color( Color ),
      Flags( Flags )
    {
    }
  };

Can somebody explain to me what on earth is the coder doing here?

a struct is being initialized with default parameters within its own definition. I fail to assimilate its semantics and usage.

Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.
Advertisement

The constructor is just a a regular function and has default parameters. The initializer list is used to initialize the members of the class using the parameters to the constructor. If you are confused by the fact that the members and the parameters have identical names, then that is no problem (using the Type member as an example):

Type(Type)

The first Type is the member to be initialized, and the second Type is the value to initialize it with. Only members can be initialized, to the first Type cannot reference anything other than the symbol that is a member of the class. The second Type follows normal name resolution rules for overloaded names: function parameters take priority over members, so the second Type resolves to the parameter to the constructor and not the member.

Tile( TileType Type = TILE_EMPTY, unsigned int Color = 0xffffffff, unsigned int Flags = 0 ) :
... I fail to assimilate its semantics and usage.
Usage, you can use any of these:

Tile t();
Tile t(TILE_WHATEVER)
Tile t(TILE_WHATEVER, 0xbaadf00d);
Tile t(TILE_WHATEVER, 0xbaadf00d, flags);

Default parameters occasionally trip up programmers over some compiler rules, and they sometimes trip up long-term growth of released APIs. It may not be a problem with a pristine code base, but as the code grows and new functions are introduced and unexpected patterns arise and all the little useful-but-ugly growths start to naturally appear, default parameters invariably end up with collisions and headaches that could have been avoided.

In the long term it is better to explicitly provide a family of functions with all the desired signatures.


    Tile(  ) ...
    Tile( TileType Type )  ...
    Tile( TileType Type, unsigned int Color) ...
    Tile( TileType Type, unsigned int Color, unsigned int Flags)  ...
Providing all the signatures is much better for long term maintenance.

In C++11, default-initializing your member-variables becomes much clearer:


struct Tile
{
    TileType      Type  = TILE_EMPTY;
    unsigned int  Color = 0xffffffff;
    unsigned int  Flags = 0;
};

In C++11, default-initializing your member-variables becomes much clearer:


struct Tile
{
    TileType      Type  = TILE_EMPTY;
    unsigned int  Color = 0xffffffff;
    unsigned int  Flags = 0;
};

This makes much more sense and is less tautological.

Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

@Servant

How is that thing called so i can google?

When that initialization takes place? Does the object needs to be fully constructed (say i want to use that variable in constructor body)?

@Servant
How is that thing called so i can google?


Non-Static Data Member Initializes, often abbreviated NSDMI.

When that initialization takes place? Does the object needs to be fully constructed (say i want to use that variable in constructor body)?


It's just syntactic sugar. These two pieces of code are functionally equivalent:

struct foo {
  int i = 0;
};

struct foo {
  int i;
  foo() : i(0) {}
};
The advantage to NSDMI shines when you have only a default constructor (easier to read) or when you have many constructors (since you don't need to repeat the initializers over and over again.

Note that these are _not_ equivalent to the original code that was posted, as the original code allows the user of the class to supply non-default data to the constructor. The above two examples both only supply a default constructor (no parameters). You can use NSDMI with the original example by doing something like:


struct foo {
  int i = 0;
  foo() = default;
  foo(int i) : i(i) {}
};
Whether you find that an improvement or not is another question.

Sean Middleditch – Game Systems Engineer – Join my team!

@Servant
How is that thing called so i can google?


Non-Static Data Member Initializes, often abbreviated NSDMI.

When that initialization takes place? Does the object needs to be fully constructed (say i want to use that variable in constructor body)?


It's just syntactic sugar. These two pieces of code are functionally equivalent:


struct foo {
  int i = 0;
};

struct foo {
  int i;
  foo() : i(0) {}
};
The advantage to NSDMI shines when you have only a default constructor (easier to read) or when you have many constructors (since you don't need to repeat the initializers over and over again.

Note that these are _not_ equivalent to the original code that was posted, as the original code allows the user of the class to supply non-default data to the constructor. The above two examples both only supply a default constructor (no parameters). You can use NSDMI with the original example by doing something like:


struct foo {
  int i = 0;
  foo() = default;
  foo(int i) : i(i) {}
};
Whether you find that an improvement or not is another question.

What does foo()=default; do?

Intel Core 2 Quad CPU Q6600, 2.4 GHz. 3GB RAM. ATI Radeon HD 3400.

What does foo()=default; do?


It's a defualted function. It tells the compiler to generate the function definition as it normally would if that function were to be defined. When you overload any constructor, C++ ellides the default constructor by default (though not the copy constructor). If you want a default constuctor, you can use =default. This has a few important differences with definining an empty default constructor wrt triviality (a new type property in C++11), eg.


// no default constructor
struct foo {
  int i;
  foo(int i) : i(i) {}
};

// trivial default constructor
struct foo {
  int i;
  foo() = default;
  foo(int i) : i(i) {}
};

// non-trivial default constructor
struct foo {
  int i = 0;
  foo() = default;
  foo(int i) : i(i) {}
};
Being trivial means there's no actual code generated. The constructor doesn't actually do anything - it doesn't set any member variables, it does nothing at all, and so optimizations can be applied that ellide the call to the default constructor entirely. The first example doesn't define a default constructor but does define another constructor, so there will be no default constructor (you must supply the argument). The third case requires the default constructor to initialize i to 0, so there is code that must run, and so it is not trivial.

You can use =default with default constructors, copy constructors and assignment operators, and move constructors and move assignment operators. You can also use =delete in order to tell the compiler not to generate a function at all even if it normally would (and this cna be used for overload handling). e.g., to make a type non-copyable in C++11, do:

struct foo {
  foo(foo const&) = delete;
  foo& operator=(foo const&) = delete;
};
That code just means that there is no copy constructor or assignment operator and any attempts to invoke them (either implicitly or explicitly) will fail at compile time (with a far better and more helpful message than you'd get with the old C++98 private copy constructor trick). You can also use it for overloading for tricky overload sets, templates, etc.

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement