[C++] Performance gap between constructor and initialization list

Comparative test of performance gap between constructor and initialization list

1. Description

In C++ classes and objects, you may have heard that it is recommended to use an initializer list to initialize intra-class members. If the member within the class is a custom type, the constructor of the custom type can only be called in the initialization list.

But is there any performance difference between the initialization list and direct assignment in the constructor body? Let’s test it today with a relatively simple code

2. Test

2.1 Code description

First, it is a custom type that implements the three functions of default construction with default values, copy construction, and assignment overloading, and adds internal printing to distinguish different functions.

struct mytest {
    
    
public:
    mytest(int a = -1)
    {
    
    
        _a = a;
        cout << "mytest() " << a << endl;
    }
    mytest(const mytest& st) {
    
    
        _a = st._a;
        cout << "mytest(copy) " << st._a << endl;
    }
    mytest& operator=(const mytest& st)
    {
    
    
        _a = st._a;
        cout << "mytest operator= " << st._a << endl;
        return *this;
    }
private:
    int _a;
};

Then use this custom type in another class

struct myclass {
    
    
public:
    myclass(const struct mytest& st, int b)
    {
    
    
        cout << "myclass() _b:" << _b << endl;
    }
    struct mytest _sa;
    int _b;
};

At this time, there are two ways to write the constructor. One is to initialize the custom type in the initialization list.

	myclass(const struct mytest& st, int b)
        :_sa(st),
        _b(b)
    {
    
    
        cout << "myclass() _b:" << _b << endl;
    }

The other is to initialize the custom type through assignment overloading in the constructor body.

    myclass(const struct mytest& st, int b)
    {
    
    
         _sa = st;
         _b = b;
        cout << "myclass() _b:" << _b << endl;
    }

It should be noted that the custom type parameters here are passed by reference, and no additional copies will be generated!

The main function body is as follows. In order to distinguish the first mytestconstructor, I added a line of output after it as a segmentation

int main()
{
    
    
    mytest test_a(1);
    cout << "------" << endl;
    myclass test(test_a, 3);
    return 0;
}

2.2 Testing

Let's first look at the method of initialization using assignment. You can see that although we have written nothing in the initialization list, the default constructor is still called here (because the default value of the default constructor is -1, here you can pass Judging from the parameters, this is not a construct we explicitly called)

After calling the default constructor, it is initialized again through assignment and overloading _sa, which is equivalent to two initializations.

image-20230824212849577

But if the initialization list is called, there will only be one copy construction, avoiding additional default construction calls !

image-20230824213021929

It has also been tested under Linux and the results are the same as VS2019.

3.Conclusion

The conclusion comes out: the initialization list can save a call to the default construction and optimize performance!

3.1 Actual scenario

In the above scenario, the performance gap may not be particularly large, but it may be different in the following scenarios.

struct mytest {
    
    
public:
    mytest(int sz = 1)
    {
    
    
        _str = new char[sz];
        _sz = sz;
        cout << "mytest() " << sz << endl;
    }
    mytest(const mytest& st) {
    
    
        delete _str;// 需要先销毁原视的数据
        _str = new char[st._sz]; // 再创建一个新的
        _sz = st._sz;
        //省略拷贝数据的代码
        cout << "mytest(copy) " << st._sz << endl;
    }
    mytest& operator=(const mytest& st)
    {
    
    
        delete _str;// 需要先销毁原视的数据
        _str = new char[st._sz]; // 再创建一个新的
        //省略拷贝数据的代码
        cout << "mytest& operator= " << st._sz << endl;
        return *this;
    }
private:
    char* _str;
    size_t _sz;
};

struct myclass {
    
    
public:
    myclass(const struct mytest& st, int b)
        :_sa(st),
        _b(b)
    {
    
    
         //_sa = st;
         //_b = b;
        cout << "myclass() _b:" << _b << endl;
    }
    struct mytest _sa;
    int _b;
};

In this scenario, because mytestthe custom type copy structure involves deep copying, the existing space needs to be destroyed, a new space created, and the data copied.

In vain, there is an extra layer of new space in the default structure + the consumption of the original space in delete in the copy structure!

If there is more than one member in the class that requires deep copying, the performance gap will be even greater!

So in C++, the initialization list always takes precedence !


By the way, here is a small pitfall of the initialization list, which can be regarded as a review;

When you use an initialization list to initialize members in a class, the order of initialization is the order in which the members are declared in the class, not the order in the initialization list ! This is very important. If the order is wrong, there may be a bug using undefined (not yet initialized) parameters!

Guess you like

Origin blog.csdn.net/muxuen/article/details/132483220