C++的构造函数总结

构造函数是C++的一个很基础的知识点,在平时编程的时候,相信大家都很熟悉,虽然比较基础,但是细究下来,还是有不少细节需要注意。这篇文章主要总结C++构造函数需要注意一些细节,一方面,可以帮助下大家巩固下这方面知识。同时,也是有助于自己更好得整理以前的知识。
  
让我们由一个对象的创建开始。当一个对象创建的时候,编译器就会调用这个对象的构造函数,在这个时候,或许大家就会有疑问了:我并没有为对象指定构造函数,那么编译器调用的构造函数由哪里来呢?

有这点疑惑就是一个好的开始,那么当我们没有指定构造函数时,编译器调用的构造函数由哪里来呢?,答案是编译器会自己为对象产生所需的构造函数。

那么现在又有了两个问题:
>1.编译器在什么条件下会为我们自动生成默认构造函数?
>
>2.自动生成的构造函数主要做了什么?

我们先来回答问题1,答案是:
>在我们没有对象指定构造函数的时候,编译器会为我们生成默认构造函数,拷贝构造函数,默认析构函数。

这样的话,无论我们使用通过new,拷贝来构造一个对象就都可以完成了。在此需要提及一下拷贝构造函数和赋值构造函数的区别,请看下例:

class Obj{}; // 声明一个对象Obj
Obj a; //调用默认构造函数来构造对象
Obj b(a);//调用默认拷贝构造函数来构造对象
Obj c = b;//调用的也是拷贝构造函数,最好将其写做 Obj c(b)。

所以,当我们不需要编译器生成的构造函数时,就应该明确说出来,即如果们声明自己的构造函数,拷贝构造函数的话,编译器就不会为我们生成这些函数了。
通过利用这点我们就可以限制对象的产生,例如,我们将默认构造函数,拷贝构造函数声明为私有,就可以防止外界来产生这个对象,这点主要是在单例模式中使用。

在上面我们了解了编译器会自动为对象生成函数的条件。下面来看第二个问题。

在这个问题中,包含了两个构造函数:默认构造函数和拷贝构造函数。下面我们将分别回答这个问题。

1.编译器生成的默认构造函数主要做了什么?

实际这个问题中的表述是不准确的,因为按照标准,编译器只在需要的时候产生才产生一个符合编译器要求的默认构造函数,其他情况下是不会产生一个默认构造函数,因为是不需要的。那么什么是需要的时候呢?答案如下:

>1.内部的成员变量拥有默认构造函数,如果有多个成员变量,那么会按照成员变量声明的顺序来调用成员变量的默认构造函数。

>2.基类拥有默认构造函数。在子类构造的时候,需要先构造父类。

>3.类中声明有虚函数,因为编译器需要为类中的虚函数表指针指定正确的地址。

>4.带有虚基类(virtual base class)。因为编译器需要确定下来虚基类在对象中的偏移,以方便调用虚基类中数据。

上面四种情况下,编译器会为对象合成默认构造函数,而通过上面的情况,也可以知道编译器合成的默认构造函数做了什么(此知识点在《深度探索C++对象模型中》详细描述)。同时也要注意一点:编译器合成的默认构造函数并没有初始化成员变量,如果要为成员变量在构造是指定特定的值,需要在自定义的构造函数中来指定。

第一个问题可以告一段落,下面我们来看默认拷贝构造函数做了什么。

回答这个问题前,我们再来回顾下拷贝构造函数的调用时机,在以下三种情况下会调用对象的拷贝构造函数

>1.以一个对象的值作为另一个对象的初值。例如:

class Obj{};
Obj a;
Obj b = a;
Obj c(a);

>2.当作为函数的参数时。例如:

class Obj{};
void Foo(Obj obj);

>3.当作为函数的返回值时。例如:

class Obj{};
Obj foo()
{
    Obj obj;
    return obj;//调用拷贝构造函数。
}

由上面第二种情况可知,**在函数中使用引用传参可以减少对象的构造**。

了解完拷贝构造函数的调用时机,我们再来看看编译器生成的默认拷贝构造函数都做了什么。默认拷贝构造函数主要作用是按位拷贝,在必要的时候,除了按位拷贝,还插入一些其他行为,具体内容请看先前的文章----[C++拷贝构造函数总结](http://www.cnblogs.com/yetuweiba/p/3390853.html)。在实现拷贝构造函数中我们需要注意一点深拷贝和浅拷贝,防止出现的拷贝不完全的错误。

以上就是构造函数的语法基础知识点,根据上面的原理,可以总结出来一些容易出错的地方,具体请看下面。

>1.如果对象拥有成员变量,需要在自定义构造函数中设定初值,尽可能地使用成员初始化列表对成员变量进行初始化。同时,成员变量是按照声明的顺序来构造的,所以,要注意依赖。

在构造函数中对成员变量进行初始化的话,在编译器中实际上是先调用此成员变量的构造函数,再调用它的赋值函数,所以,使用成员初始化列表会节省效率。

成员变量的构造是按照声明的顺序进行的,所以,不要让声明早的成员变量依赖声明晚的成员变量。示例如下

class Obj
{
private:
    int a;
    int b;
// 下面的构造函数是错误的
Obj(const int value)
: a(b + 1)    // error,此时b还未构造好,a的值会不可预料。
, b(value) // 正确的做法是调换a和b的声明顺序,在成员初始话列表中也调换a和b的顺序
{}
};

>2.谨慎在构造函数中调用虚函数(最好不要在构造函数中调用虚函数)。

我们先来举一个例子:

class Base
{
    Base()
    {
        fun();//error,此时调用的是Base的fun,会发生调用错误。
    }
    virtual void fun() = 0;
};
class Derivate : public Base
{
    Derivate(){};
    virtual void fun()
    {
        std::cout << "I am Derivate" << std::endl;
    }
};

Derivate d;

上面代码中,构造d会发生错误,因为基类早于派生类进行构造,在基类构造的时候,是不会下降到派生类中的,也就是此时只会调用基类的函数,而Base中的fun是存虚函数,此时就会发生错误。虽然我们可以在Derivate的构造函数中调用fun,但这样做,就意味着接口的设计出现了问题。所以,最好不要在构造函数中调用虚函数。

>3.在并发环境下,注意构造函数的安全性。

当在并发的环境下,原本简单的事情就会变的复杂。我们都知道,在并发环境下,线程的执行是乱序的,我们拿到一个指向对象的指针,这个指针指向的可能是构造函数执行一般的对象,例如:

class Foo : public Observer
{
    // error
    Foo(Observable * s)
    {
        s->register(this)
    }
}

上面例子的做法是不安全的,因为在构造函数中将自身注册出去,此时对象可能仍未完全构造完。如果,Foo是积累的话,那么注册的时候派生类还没有构造完成,此时也会引发错误。所以,由上例可知:在对象构造期间不要对外暴露this指针(陈硕)。解决上面错误的一个方法就是“二段式构造”,即对象构造完成后,再对外暴露this指针。


以上就是关于C++中构造函数的一些总结,如果有错误的地方,请大家多多指教,谢谢。另外,不支持Markdown,有些不方便。

时间: 2024-10-24 15:39:20

C++的构造函数总结的相关文章

【转载】C++拷贝构造函数(深拷贝,浅拷贝)

对于普通类型的对象来说,它们之间的复制是很简单的,例如:int a=88;int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. #include <iostream>using namespace std;class CExample {private:     int a;public:     CExample(int b)     { a=b;}     void Show ()     {        cout<

C++程序设计方法3:移动构造函数

移动拷贝构造函数 语法: ClassName(ClassName&&); 目的: 用来偷"临时变量"中的资源(比如内存) 临时变量被编译器设置为常量形式,使用拷贝构造函数无法将资源偷出来("偷"是对原来对象的一种改动,违反常量的限制) 基于"右值引用"定义的移动构造函数支持接受临时变量,能偷出临时变量中的资源: #include <iostream> using namespace std; class Test {

父类的构造函数在对象的生命中扮演的角色

在创建新对象时,所有继承下来的构造函数都会被继承 这代表着每个父类都有一个构造函数(因为每个类至少都会有一个构造函数),并且每个构造函数都会在对象创建时执行. 执行new的指令是个大事件,因为他会启动构造函数的连锁反应.还有,就算是抽象的类也有构造函数.虽然你不能对抽象函数执行new操作,但是抽象函数还是父类,因此它的构造函数会在具体的子类创建实例时被执行. 在构造函数中用super调用父类的构造函数部分(注意:调用super() 方法是调用父类构造函数的唯一方法).要记得子类会根据父类的状态进

JavaScript中的构造函数

function Accom(){};    //创建一个构造函数 //创建两个对象 var house=new Accom(); var apartment=new Accom(); 通过构造函数创建的对象有一个属性constructor,这个属性指向创建该对象时所用的Javascript构造函数. house.constructor===Accom;  或者   house instanceof Accom;     //true JavaScript中的每个构造函数都有一个prototyp

javascript中的构造函数和原型及原型链

纯属个人理解,有错误的地方希望大牛指出,以免误人子弟 1.构造函数: 构造函数的作用 : 初始化由new创建出来的对象    new 的作用: 创建对象(空对象) new 后面跟的是函数调用,使用new来调用函数,跟普通的直接调用函数主要的不同: 就是 this 的指向不同了 , 再就是 会自动的返回新创建的对象 什么是原型?        原型的作用:就是为了实现继承!  一个对象的原型就是它的构造函数的prototype属性的值. 在讨论原型的时候,是指的 对象和原型对关系 prototyp

Resolve Type中构造函数传值

1 1 class Test 2 { 3 4 private ITeacher _teacher; 5 private IStudent _student; 6 public Test(ITeacher tea, IStudent stu) 7 { 8 _teacher = tea; 9 _student = stu; 10 } 11 } 2 1 class Student:IStudent 2 { 3 4 public Student() 5 { 6 int i = 1; 7 } 8 9 10

构造函数、拷贝构造函数、赋值操作符

对于这样一种类与类之间的关系,我们希望为其编写“深拷贝”.两个类的定义如下: class Point { int x; int y; }; class Polygon : public Shape { Point *points; }; 1. 构造函数 //构造函数 Polygon(const Point &p) : _point(new Point) { this->_point->x = p.x; this->_point->y = p.y; } 2. 拷贝构造函数 /

JAVA_SE基础——29.构造函数

黑马程序员入学Blog... jvm创建Java对象时候需要调用构造器,默认是不带参数的.在构造器中,你可以让jvm帮你初始化一些参数或者执行一系列的动作. 它是对象创建中执行的函数,及第一个被执行的方法 特点: 1.函数名与类名相同. 2.不用定义返回值类型. 3.没有具体的返回值. P.S. 在构造函数前面加上返回值就只是一般函数了. 作用:给对象进行初始化. class Person{ private String name ; private int age ; //定义一个Person

C++ 构造函数和析构函数

构造函数: 作用: 1)分配空间:分配非静态数据成员的存储空间 2)初始化成员:初始化非静态数据成员 分配空间: 1)含有指针变量,需要程序员显式申请空间(使用new申请) 2)非指针变量:由系统自动分配空间 初始化成员: 1)使用赋值语句初始化:一般的变量 2)使用表达式表初始化:一般的变量 +  Const成员,引用成员,对象成员 调用时机:在定义对象的时候,系统自动调用构造函数 1)对象被创建时,自动调用构造函数 Coord p1(1); Coord p2=1;  //此时也会调用构造函数

构造函数和析构函数是否可以被重载

构造函数可以被重载,因为构造函数可以有多个且可以带参数. 析构函数不可以被重载,因为析构函数只能有一个,且不能带参数.