C++成员变量初始化-就地初始化&初始化列表

就地初始化&初始化列表

就地初始化:member initializer list

初始化列表:member initializer list,或:member initialization list

 

参考:https://www.cnblogs.com/lidabo/p/3628987.html

C++构造函数初始化按下列顺序被调用:

  • 首先,任何虚拟基类的构造函数按照它们被继承的顺序构造;
  • 其次,任何非虚拟基类的构造函数按照它们被继承的顺序构造;
  • 最后,任何成员对象的构造函数按照它们声明的顺序调用;

 

由于类成员初始化总在构造函数执行之前,编译器总是确保所有成员对象在构造函数体执行之前初始化,

参考:https://www.cnblogs.com/simplepaul/p/7635648.html

 

在C++11中,直接对成员变量赋值(就地初始化),和初始化列表,哪个先执行,哪个后执行?

例如:

class MyClass
{
private:
    int a = 10;
public:
    MyClass()
        :a(20)
    {}
    ~MyClass(){}

    int getA()
    {
        return a;
    }
};

  构造一个MyClass后,a的值是多少?

条款4:C++“成员初始化次序”:base class更早于其derived class被初始化,而class的成员变量总是以其声明次序被初始化,即使成员变量在成员初始化列表中的次序不同。

这里只是说了成员初始化的顺序

  但是:在构造MyClass中,已经初始化了a,然后再在初始化列表中调用拷贝构造函数吗?待查

 写了如下测试代码:

class B
{
public:
    explicit B(int a)
    {
        this->b = a;
        cout << "construct:" << a << endl;
    }

    B(const B &val)
    {
      cout << "copy:" << val.b << endl;
      this->b = val.b;
    }

~B()
    {
        cout << "Destruct:" << this->b << endl;
    }
private:
    int b;
};
class MyClass
{
private:
    int a = 10;
    B b = B(3);
public:
    MyClass()
        :a(20),
        b(B(4))
    {}
    ~MyClass(){}

    int getA()
    {
        return a;
    }
};
int main()
{
    MyClass aa;
    cout << "val a:" << aa.getA() << endl;
    int val = aa.getA();
}

输出的打印,类B只构造了一次。。。即成员初始化列表中的4那次。

为啥嘞???

相关文章:https://cloud.tencent.com/developer/article/1394301

这篇文章说的是,就地初始化在先,然后成员列表初始化: 

  就地初始化与初始化列表的先后顺序

  C++11标准支持了就地初始化非静态数据成员的同时,初始化列表的方式也被保留下来,也就是说既可以使用就地初始化,也可以使用初始化列表来完成数据成员的初始化工作。当二者同时使用时,并不冲突,初始化列表发生在就地初始化之后,即最终的初始化结果以初始化列表为准。

Note:

  上面链接的文章说的不准确,参考:https://en.cppreference.com/w/cpp/language/constructor,这个文章中说,如果成员变量就地初始化,成员变量又出现在初始化列表中,则就地初始化被忽略,以初始化列表中的为准

If a non-static data member has a default member initializer and also appears in a member initializer list, then the member initializer is used and the default member initializer is ignored:

struct S {
    int n = 42;   // default member initializer
    S() : n(7) {} // will set n to 7, not 42
};

   该文章中对构造函数解释如下:

  构造函数没有函数名且不能直接被调用。构造函数在初始化发生时被调用,且根据初始化的规则选择特定的构造函数。没有explicit说明符的是可以隐式转换的构造函数。带有constexpr说明符的构造函数成为一个字面类型(LiteralType)。没够任何参数的构造函数为默认构造函数 default constructor。以另一个同类型对象作为的参数的构造函数有拷贝构造函数 copy constructor,和移动构造函数 move constructor。

  在构造函数的函数体执行之前(即{}内的语句),所有的父类、虚类、non-static成员变量都已初始化完成。成员初始化列表(member initializer list)可用于父类、虚类、非静态成员变量的初始化(非默认的初始化,non-default initialization)。对于不能调用默认构造函数构造的基类、non-static成员变量,必须在初始化列表中进行初始化,例如:引用类型、const类型的成员变量。

 什么情况必须使用初始化列表

  什么时候必须使用初始化列表?《深入理解C++对象模型》中描述如下:

 成员初始化顺序

  如果在构造函数中,通过赋值的形式对成员变量进行初始化,如下所示:

  这种情况,构造函数可能的内部扩张结果为:

   也就是说,在构造函数中,编译器先调用默认构造函数初始化了 _name ,_cnt,然后再通过赋值操作符(operator =)对其进行赋值操作,效率较差。

  而采用初始化列表形式,如:

   它会被编译器扩张为如下代码:

 

   即直接调用拷贝构造函数(copy constructor)对成员变量进行构造,效率较高。(注意:不论在初始化列表中写的初始化顺序是什么,成员变量的初始化顺序按照在类的声明中的顺序进行,条款4)

  在本例中,_name 先于 _cnt声明,则 _name先于_cnt初始化,“初始化次序” 和 “initialization list中的项目排列次序”是不同的

 构造函数调用构造函数

  在C++11之后,可以在构造函数中调用构造函数:如果一个类中有多个构造函数,为避免代码的重复,可以在一个构造函数中的初始化列表中调用另一个构造函数,但是注意构造函数调用不能出现循环情况。例如:

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};

 

下面是cppreference给出的示例代码,包含大部分情况:

#include <fstream>
#include <string>
#include <mutex>
 
struct Base {
    int n;
};   
 
struct Class : public Base
{
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f;
    std::string s;
 
    Class ( int x )
      : Base { 123 }, // initialize base class
        x ( x ),      // x (member) is initialized with x (parameter)
        y { 0 },      // y initialized to 0
        f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized
        s(__func__),  //__func__ is available because init-list is a part of constructor
        lg ( m ),     // lg uses m, which is already initialized
        m{}           // m is initialized before lg even though it appears last here
    {}                // empty compound statement
 
    Class ( double a )
      : y ( a+1 ),
        x ( y ), // x will be initialized before y, its value here is indeterminate
        lg ( m )
    {} // base class initializer does not appear in the list, it is
       // default-initialized (not the same as if Base() were used, which is value-init)
 
    Class()
    try // function-try block begins before the function body, which includes init list
      : Class( 0.0 ) //delegate constructor
    {
        // ...
    }
    catch (...)
    {
        // exception occurred on initialization
    }
};
 
int main() {
    Class c;
    Class c1(1);
    Class c2(0.1);
}

 

posted @ 2021-01-07 20:37  adfas  阅读(1924)  评论(0编辑  收藏  举报