开发者

Class construction with initial values

开发者 https://www.devze.com 2023-03-31 14:53 出处:网络
I\'m new to C++, and the whole idea of classes - I\'m still reading a book to try and learn. The book I\'m reading says that when I construct a class, I can assign default values by doing this:

I'm new to C++, and the whole idea of classes - I'm still reading a book to try and learn. The book I'm reading says that when I construct a class, I can assign default values by doing this:

class foo {
public:
   foo(char c, int i);
private:
   char exampleChar;
   int exampleInt;
};

foo::foo(char c, int i):
exampleChar(c),
exampleInt(i)
{}

This code (to me) looks very messy, and doesn't follow rules that I'm used to in other languages. My questi开发者_如何学编程on is, what's the difference between doing the above, and this (below, which I personally think looks a lot cleaner)?

foo::foo(char c, int i) {
   exampleChar = c;
   exampleInt = i;
}

Sort of things I'm thinking about are: are there performance/efficiency issues if done on a large scale - or is it exactly the same?


The first way, by doing : exampleChar(c), exampleInt(i) is called an initializer list.

If you do it the second way, the two variables are default constructed first1, then you assign them a value. (When the actual body of the constructor is entered, anything that hasn't been initialized by the initializer list is default constructed.) This is a waste of time because you're just overwriting the values anyway. For small types like int or char this isn't a big deal, but when those member variables are large types that would take lots of cycles to construct, you definitely want to use the initializer list.

The second way won't waste time giving them a default value and then overwriting it - it will set their values directly to that value you give it (or call the right constructor if the member is an object).

You can see what we mean by doing this:

class MyClass {
public:
    int _i; // our data

    // default constructor
    MyClass() : _i(0) { cout << "default constructor"; }

    // constructor that takes an int
    MyClass(int i) : _i(i) { cout << "int constructor"; }

    // assignment operator
    void operator=(int i) { _i = i; cout << "assignment operator"; }
};

class OtherClass {
public:
    MyClass c;

    OtherClass() {
        c = 54;
    }
};

OtherClass oc;

You'll see that

default constructor
assignment operator

is printed. That's two function calls which, for other classes, could be expensive.

If you change the constructor of OtherClass to

OtherClass() : c(54) {   }

You'll see that

int constructor

is printed. Just one call compared to two. This is the most efficient way.

Initializer lists are also a must when you

  1. have types that have no default constructor. You have to call the right constructor in the initializer list.

  2. have a const member that you want to give some value (rather than just have permantently the default value

  3. have a reference member. You must use initializer lists on these.

tl;dr: do it because it's at least as fast but never slower than the other way, and sometimes far faster.

1 For built in types like int and char, they are actually not constructed at all; they just have the value of whatever memory they happen to have had previously.


The difference is that the compiler will always initialize all members (in declaration order) prior to the first user-defined constructor statement. In the case of a char and an int, which are both primitive types, 'initialization' actually means 'no initialization' here. However, if you have a member with a constructor that does some actual work, this constructor will be called upfront - if you do

foo::foo() {
    myComplexMember = MyComplexClass(42);
}

the compiler did already invoke the MyComplexClass default constructor before your code got called, which is a waste of resources (and a compiler error if the default ctor is not accessible).

By using an initialization list, you can customize the default initialization and avoid doing things for nothing. Obviously, this is the way to go.


There are things you can do like that that you couldn't otherwise.

  1. If one of the members doesn't have a default constructor. That's the only way you could initiate the member at construction. (same goes to base class)

  2. You can assign a value to a const member.

  3. You can assure a defined state for the class before the constructor function starts running.

  4. If a member is a reference it needs to be initialized in the Initialization List. Because references are immutable and can be initialized only once in the beginning (like const)


Well, this is a typical FAQ question: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6

In your case, using char and int there are no differences.

General rule: use initialization list as possibile, there are very few cases when you can prefer assignment, for example for improve readability:

MyClass::MyClass
{
  a = b = c = d = e = f = 0;
}

is better than

class MyClass::MyClass : a(0), b(0), c(0), d(0), e(0), f(0) { }


If the members had non-trivial constructors, in the code below first the default constructors would be called, then the assignments would be executed, while in the code above they would be initialized only one time. So yes, there may be a performance issue.

There is also a practical issue: if they are const, references, or don't have default constructors, you can't use the version below.


There is a subtle, but important difference between those two options. What you have at the top is called a member initialization list. When the object is created, the members in this list are initialized to whatever you put in the parenthesis.

When you do assignment in the constructor body, the values are being first initialized, and then assigned. I'll post a short example below.

Example 1: Member initialization

class foo 
{
public:
   foo(char c, int i);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i):
    exampleChar(c),
    exampleInt(i),
    exampleBar()     //Here, a bar is being default constructed
{
}

Example 2: Constructor Assignment

class foo 
{
public:
   foo(char c, int i, Bar b);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i, Bar b):
    //exampleChar(c),
    //exampleInt(i),
    //exampleBar()  
{
    exampleChar = c;
    exampleInt = i;
    exampleBar = someOtherBar;  //Here, a bar is being assigned
}

This is where it gets interesting. Note that exampleBar is being assigned. Behind the scenes, a Bar is actually first being default constructed, even though you did not specify that. Furthermore, if your Bar is more complicated then a simple struct, you will need to be sure to implement the assignment operator in order for you to initialize it in this way. Even furthermore, in order to initialize the Bar from another Bar from the member initialization list, you must implement the copy constructor!

Example 3: Copy constructor used in member init

class foo 
{
public:
   foo(char c, int i, Bar b);

private:
   char exampleChar;
   int exampleInt;
   Bar exampleBar;
};

foo::foo(char c, int i, Bar b):
    //exampleChar(c),
    //exampleInt(i),
    exampleBar(b)     //Here, a bar is being constructed using the copy constructor of Bar
{
    exampleChar = c;
    exampleInt = i;
}


I would get into the habit of using initialisation lists. They will not suffer from problems when somebody changes a char to some object where the default constructor is called first, and also for const correctness for the const values!


foo::foo(char c, int i):exampleChar(c),exampleInt(i){}

This construct is called a Member Initializer List in C++.

It initializes your member exampleChar to a value c & exampleInt to i.


What is the difference between Initializing And Assignment inside constructor? &
What is the advantage?

There is a difference between Initializing a member using initializer list and assigning it an value inside the constructor body.

When you initialize fields via initializer list the constructors will be called once.

If you use the assignment then the fields will be first initialized with default constructors and then reassigned (via assignment operator) with actual values.

As you see there is an additional overhead of creation & assignment in the latter, which might be considerable for user defined classes.

For an integer data type(for which you use it) or POD class members there is no practical overhead.

0

精彩评论

暂无评论...
验证码 换一张
取 消