ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

复制构造函数被自动调用的时机

2022-04-22 21:01:19  阅读:152  来源: 互联网

标签:lea 调用 name person mov Person 复制 rsp 构造函数


对象参数传参时

示例代码

#include <stdio.h>
#include <string.h>

class Person {
public:
  Person() {
    name = NULL;//无参构造函数,初始化指针
  }
  Person(const Person& obj) {
    // 注:如果在拷贝构造函数中直接复制指针值,那么对象内的两个成员指针会指向同一个资源,这属于浅拷贝
    // this->name = obj.name;
    // 为实参对象中的指针所指向的堆空间制作一份副本,这就是深拷贝了
    int len = strlen(obj.name);
    this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
    strcpy(this->name, obj.name);
  }
  ~Person(){					// 析构函数,释放资源
    if (name != NULL) {
      //如果使用浅拷贝,执行到这里会产生错误,因为源对象和复制的对象在作用域结束时会调用到此处,所以会产生同一个资源释放两次的错误。
      delete[] name;		
      name = NULL;
    }
  }


  void setName(const char* name) {
    int len = strlen(name);
    if (this->name != NULL) {
      delete [] this->name; 
    }
    this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
    strcpy(this->name, name);
  }
public:
  char * name;
};

void show(Person person){	// 参数是对象类型,会触发拷贝构造函数
  printf("name:%s\n", person.name);
}

int main(int argc, char* argv[]) {
  Person person; 
  person.setName("Hello");
  show(person);
  return 0;
}

汇编

mov     [rsp+arg_8], rdx
mov     [rsp+arg_0], ecx
sub     rsp, 48h
lea     rcx, [rsp+48h+var_20]
call    String_constructor ; Microsoft VisualC v14 64bit runtime
                        ; Microsoft VisualC 64bit universal runtime
lea     rdx, aHello     ; "Hello"
lea     rcx, [rsp+48h+var_20]
call    String_set
lea     rax, [rsp+48h+var_10]
mov     [rsp+48h+var_18], rax
lea     rdx, [rsp+48h+var_20]
mov     rcx, [rsp+48h+var_18]
call    String_constructor_copy
mov     rcx, rax
call    show
mov     [rsp+48h+var_28], 0
lea     rcx, [rsp+48h+var_20]
call    String_dectructor
mov     eax, [rsp+48h+var_28]
add     rsp, 48h
retn

上面对象var_20的地址被作为参数,被var_18的构造函数调用,然后var_18的地址作为show的参数,但是实际这里用c++代码只有下面这个一句

 show(person);

对象值传递会先调用复制构造函数初始化一个临时对象(参数对象),然后将这个临时对象的地址传入目标函数(show)使用,注意这个参数对象的生存周期只存在与目标函数(show),虽然对象的位置是在上一层(main)的栈帧中。

看show的汇编代码

mov     [rsp+arg_0], rcx
sub     rsp, 28h
mov     rax, [rsp+28h+arg_0]
mov     rdx, [rax]
lea     rcx, Format     ; "name:%s\n"
call    printf
mov     rcx, [rsp+28h+arg_0]
call    String_dectructor
add     rsp, 28h
retn

在show函数中直接把参数对象析构了

如果没有定义复制构造函数,就会直接内存浅拷贝。

综上所述,对象在传参之前会调用内存拷贝函数复制一份对象,然后将该对象的地址赋值给函数,并在函数内析构。

返回对象时

示例代码

#include <stdio.h>
#include <string.h>

class Person {
public:
  Person() {
    name = NULL;//无参构造函数,初始化指针
	printf("ctor\n");
  }
 Person(const Person& obj) {
    // // // 注:如果在拷贝构造函数中直接复制指针值,那么对象内的两个成员指针会指向同一个资源,这属于浅拷贝
this->name = obj.name;
    // // // 为实参对象中的指针所指向的堆空间制作一份副本,这就是深拷贝了
   int len = strlen(obj.name);
  this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
   strcpy(this->name, obj.name);
   printf("copy ctor\n");
  }
  ~Person(){					// 析构函数,释放资源
    if (name != NULL) {
      //如果使用浅拷贝,执行到这里会产生错误,因为源对象和复制的对象在作用域结束时会调用到此处,所以会产生同一个资源释放两次的错误。
      delete[] name;		
      name = NULL;
    }
  }


  void setName(const char* name) {
    int len = strlen(name);
    if (this->name != NULL) {
      delete [] this->name; 
    }
    this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
    strcpy(this->name, name);
  }
public:
  char * name;
};

Person getObject() {	
  Person person;   //定义局部对象
  person.setName("Hello");
  return person;              
}

int main(int argc, char* argv[]) {
 // Person person;
 // person = getObject();
 getObject();
  return 0;
}

main

mov     [rsp+arg_8], rdx
mov     [rsp+arg_0], ecx
sub     rsp, 38h
lea     rcx, [rsp+38h+var_18]
call    getObject
lea     rcx, [rsp+38h+var_18]
call    Person_dtor
xor     eax, eax
add     rsp, 38h
retn

getObject

mov     [rsp+arg_0], rcx
sub     rsp, 38h
lea     rcx, [rsp+38h+var_18]
call    Person_ctor
lea     rdx, aHello     ; "Hello"
lea     rcx, [rsp+38h+var_18]
call    Person_setName
lea     rdx, [rsp+38h+var_18]
mov     rcx, [rsp+38h+arg_0]
call    Person_ctor_copy
lea     rcx, [rsp+38h+var_18]
call    Person_dtor
mov     rax, [rsp+38h+arg_0]
add     rsp, 38h
retn

可以看到在main函数中,编译器分配了一个临时对象,将它的地址传入getObject,并在getObject调用了赋值构造函数,并返回了该临时对象的地址,该临时对象在main函数中执行完getObject后立即析构

有一种特殊情况,不会产生临时对象,或者说将临时对象的生命周期延长

mian函数c++代码


int main(int argc, char* argv[]) {
  
 Person person = getObject();
 person.setName("你好");
  return 0;
}

main函数汇编代码

mov     [rsp+arg_8], rdx
mov     [rsp+arg_0], ecx
sub     rsp, 38h
lea     rcx, [rsp+38h+var_10]
call    getObject
lea     rdx, asc_1400132F8 ; "你好"
lea     rcx, [rsp+38h+var_10]
call    Person_setName
mov     [rsp+38h+var_18], 0
lea     rcx, [rsp+38h+var_10]
call    Person_dtor
mov     eax, [rsp+38h+var_18]
add     rsp, 38h
retn

可以看到临时对象在getObject结束以后并没有立即析构,而是在main函数结束以后才析构

综上所述,若编译器检测到返回对象时,函数内部会自动生成对象指针参数,并使用对象指针参数调用复制构造函数,并将该指针返回,而外部则分配一个临时对象,并在调用完后,立即析构,若有变量在声明时承接函数返回结果,则将该变量当作临时变量,生命周期和正常的局部对象一样

对象赋值时

代码示例

#include <stdio.h>
#include <string.h>

class Person {
public:
  Person() {
    name = NULL;//无参构造函数,初始化指针
	printf("ctor\n");
  }
 Person(const Person& obj) {
    // // // 注:如果在拷贝构造函数中直接复制指针值,那么对象内的两个成员指针会指向同一个资源,这属于浅拷贝
this->name = obj.name;
    // // // 为实参对象中的指针所指向的堆空间制作一份副本,这就是深拷贝了
   int len = strlen(obj.name);
  this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
   strcpy(this->name, obj.name);
  }
  ~Person(){					// 析构函数,释放资源
    if (name != NULL) {
      //如果使用浅拷贝,执行到这里会产生错误,因为源对象和复制的对象在作用域结束时会调用到此处,所以会产生同一个资源释放两次的错误。
      delete[] name;		
      name = NULL;
    }
  }


  void setName(const char* name) {
    int len = strlen(name);
    if (this->name != NULL) {
      delete [] this->name; 
    }
    this->name = new char[len + sizeof(char)]; // 为了便于讲解,这里没有检查指针
    strcpy(this->name, name);
  }
//void operator = (Person & obj){};

public:
  char * name;
};

Person getObject() {	
  Person person;   //定义局部对象
  person.setName("Hello");
  return person;              
}


int main(int argc, char* argv[]) {
  
 Person person;
 person.setName("你好");
 Person person2;
 person2 =person;
 printf("%x\n",person.name);
 printf("%x\n",person2.name);
  return 0;
}

对象赋值时有两种情况,一种是会自动调用赋值构造函数,一种会调用operator =函数,由于这两个成员函数的默认代码是一样的浅复制内存,所以非常容易搞混

自动调用赋值构造函数

main c++

int main(int argc, char* argv[]) {
  
 Person person;
 person.setName("你好");
 Person person2 = person;
 
 printf("%x\n",person.name);
 printf("%x\n",person2.name);
  return 0;
}

汇编

push    ebp
mov     ebp, esp
sub     esp, 0Ch
lea     ecx, [ebp+person]
call    Person_ctor
push    offset asc_412168 ; "你好"
lea     ecx, [ebp+person]
call    Person_setName
lea     eax, [ebp+person]
push    eax
lea     ecx, [ebp+person2]
call    Person_ctor_copy
mov     ecx, [ebp+person]
push    ecx
push    offset Format   ; "%x\n"
call    _printf
add     esp, 8
mov     edx, [ebp+person2]
push    edx
push    offset asc_412174 ; "%x\n"
call    _printf
add     esp, 8
mov     [ebp+var_C], 0
lea     ecx, [ebp+person2]
call    Person_dtor
lea     ecx, [ebp+person]
call    Person_dtor
mov     eax, [ebp+var_C]
mov     esp, ebp
pop     ebp
retn

可以看到,当对象声明的时候进行对象赋值时,此时会自动调用复制构造函数

自动调用operator =

main c++

int main(int argc, char* argv[]) {
  
 Person person;
 person.setName("你好");
 Person person2;
 person2 =person;
 printf("%x\n",person.name);
 printf("%x\n",person2.name);
  return 0;
}

汇编

push    ebp
mov     ebp, esp
sub     esp, 0Ch
lea     ecx, [ebp+person]
call    Person_ctor
push    offset asc_412168 ; "你好"
lea     ecx, [ebp+person]
call    Person_setName
lea     ecx, [ebp+person2]
call    Person_ctor
lea     eax, [ebp+person]
push    eax
lea     ecx, [ebp+person2]
call    operator等于
mov     ecx, [ebp+person]
push    ecx
push    offset Format   ; "%x\n"
call    _printf
add     esp, 8
mov     edx, [ebp+person2]
push    edx
push    offset asc_412174 ; "%x\n"
call    _printf
add     esp, 8
mov     [ebp+var_C], 0
lea     ecx, [ebp+person2]
call    Person_dtor
lea     ecx, [ebp+person]
call    Person_dtor
mov     eax, [ebp+var_C]
mov     esp, ebp
pop     ebp
retn

可以看到,person和person2构造之后,person2就调用了operator = 函数

综上所述,单纯的对象赋值并不会触发复制构造函数,而是触发了operator = 函数,只有对象声明时发生复制才会触发复制构造函数

标签:lea,调用,name,person,mov,Person,复制,rsp,构造函数
来源: https://www.cnblogs.com/czlnb/p/16180486.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有