C++拷贝控制技术

Posted lvshy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++拷贝控制技术相关的知识,希望对你有一定的参考价值。

  1. 模板特例如何写忘了
  2. 拷贝noexcept

     如果可以确认不会抛出异常,交换两个指针就行了

  3. swap是命名空间里的某个,更改的时候需要加上该命名空间

     特例函数怎么写,就这样。inline的位置值得注意

  4. "=”运算符重载

  5.  

    两种拷贝一致 

  6. 不可拷贝的方式

    1.   编译器会想方设法为我们设置一个默认的拷贝
    2. 有一个不可拷贝的成员对象
    3. 继承自一个不可拷贝的类
    4. 任何对象至少有一字节 空的类也不会0
    5. 空基类优化  继承自一个空类它就真的是空的,编译器帮我们优化
    6. 所以我们采用继承的方式

       因为它的基类无法生成拷贝

  7. 继承是个秘密

     任何依赖于知道继承的关系的操作都不行了

  8. 抽象类操作
    1.   返回值可以是对象的指针,要与抽象类一致或者是斜变
    2.  

    3. const要从逻辑来考虑,不是仅仅是元素

       按理来说可以写成const因为shape类的数据成员是bp,bp其实没有被改变,没有被改变意思是说吧bp指向的对象没变,只是对象的数据改变了。但是用户理解是它伸缩了

  9. 代理类surrogate
    1.   支持动态绑定
    2. 内存管理,不用手动释放

  10. 引用计数
    1.   一个人什么时候真正死亡,是他的肉体死亡吗?不是,当这个世界上没有任何一个人再记住他的时候
    2. 值语义、引用语义
    3. 句柄:一种代理类 添加引用计数,当计数为0销毁对象
    4. 懒惰赋值

  11. 拷贝新的开始 没有销毁就不会创建
    1.   发生了几次拷贝

       自17起不再把返回值拷贝

    2. 拷贝控制
      1.   拷贝构造函数

         写参数的时候一定要首先考虑可不可以用常量引用,如果不写引用,他会首先发生临时变量赋值,这时候就会调用拷贝构造函数,无限递归了。如果不写const首先他不规范,其次去除底层const

      2. 拷贝复制运算符
        1.  返回this引用可以连续赋值,这是一种规范

        2. 赋值运算符重载一定要避免自我复制 V1=V1看起来很蠢,但是V1可能等于它的引用

           当首先销毁自身时也同时销毁了赋值对象

        3.  正确的做法 先复制,异常安全

        4. 不是异常安全的看似的做法

  12. 默认的拷贝构造
    1.   逐个成员复制
    2. 类对象,调用类的拷贝构造
    3. 不可拷贝对象,没办法也不报错
    4. 数组成员可以正确拷贝,编译器知道它是数组,指针就不是数组
  13. 默认函数,编译器有一定能力合成的函数=default

  14. 三大律

     这个类有一定的资源,或者这三个函数不好写、

  15. 禁止拷贝
    1.   比如输入流cin
    2. =delete 所有函数都可以=delete

       如果编译器帮你生成默认函数发生错误时隐式delete

    3. 之前是把拷贝弄成private,类内或者friend 只声明不定义,编译器发生连接错误,连接错误是运行错误,不是编译错误
  16. explicit声明

     显式构造,不允许隐式类型转换

  17. logic const

     不是const,可变对象,这个对象可不变对用户没有任何影响,在逻辑上依然是const,但是如果不写mutable肯定是不行的,因为编译器不允许在const里更改修改值

  18. decltype类型推导

     只用类型,不去计算,完完整整保留

     i加个()就是表达式,表达式的返回值就是左值是引用

拷贝控制C++

第13章 拷贝控制

从此章我们即将开始第三部分的学习,之前我们已经学过了两个部分,C++基础和C++标准库,第三部分为类设计者的工具
也就是我们即将开始传说中的对象对象编程之旅,面向对象程序设计(Object Oriented Programming)

本章进行学习类如何操控该类型的拷贝,赋值,移动或者销毁,有:拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符以及析构函数等重要知识

拷贝构造函数

定义:如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是构造拷贝函数

简单上手

//example1.cpp
class Person

public:
    int age;
    Person() = default;
    Person(int age) : age(age) 
    Person(const Person &person)
    
        //内容拷贝
        this->age = person.age;
    
;

int main(int argc, char **argv)

    Person person1(19);
    Person person2 = person1;
    cout << person2.age << endl; // 19
    return 0;

合成拷贝构造函数

默认情况下,编译器会定义一个拷贝构造函数,即使在我们提供拷贝构造函数的情况下也仍会自动生成,默认情况下会将每个非static成员拷贝到正在创建的对象中

//example2.cpp
class Person

public:
    int age;
    string name;
    Person() = default;
    Person(const Person &);
    Person(const int age, const string name) : age(age), name(name)
    
    
;

//直接使用构造函数初始化列表
//此定义与默认合成拷贝函数相同
Person::Person(const Person &person) : age(person.age), name(person.name)



int main(int argc, char **argv)

    Person me(19, "gaowanlu");
    Person other = me;
    // 19 gaowanlu
    cout << other.age << " " << other.name << endl;
    return 0;

尝试测试一下编译器默认提供的合成拷贝构造函数,可见存在默认合成拷贝构造函数
如果不想让一个构造函数具有可以赋值转换的功能,则将其定义为explicit的

//example3.cpp
class Person

public:
    string name;
    int age;
    Person(const int age, const string name) : name(name), age(age) 
;

int main(int argc, char **argv)

    Person me(19, "gaowanlu");
    Person other = me;
    // 19 gaowanlu
    cout << other.age << " " << other.name << endl;
    return 0;

重载赋值运算符

重载operator=方法进行自定义赋值运算符使用时要做的事情

//example4.cpp
class Person

public:
    int age;
    string name;
    Person() = default;
    Person(int age, string name) : age(age), name(name) 
    Person &operator=(const Person &);
;

Person &Person::operator=(const Person &person)

    cout << "operator =" << endl;
    this->age = person.age;
    this->name = person.name;
    return *this;


int main(int argc, char **argv)

    Person person1(19, "me");
    Person person2;
    person2 = person1;                                  // operator =
    cout << person2.age << " " << person2.name << endl; // 19 me
    return 0;

合成拷贝赋值运算符

与合成拷贝构造函数类似,如果没有自定义拷贝赋值运算符,编译器会自动生成

//example5.cpp
class Person

public:
    int age;
    string name;
    Person() = default;
    Person(int age, string name) : age(age), name(name) 
;

int main(int argc, char **argv)

    Person person1(19, "me");
    Person person2;
    person2 = person1;                                  //使用默认合成拷贝赋值运算符
    cout << person2.age << " " << person2.name << endl; // 19 me
    return 0;

析构函数

析构函数与构造函数不同,构造函数初始化对象的非static数据成员,还可能做一些在对象创建时需要做的事情。析构函数通常释放对象的资源,并销毁对象的非static数据成员

~TypeName();析构函数没有返回值,没有接收参数,所以其没有重载形式

在构造函数中,初始化部分执行在函数体执行前,析构函数则是首先执行函数体,然后按照初始化顺序的逆序销毁。

构造函数被调用的时机

  • 变量在离开其作用域时被销毁
  • 当一个对象被销毁时,其成员被销毁
  • 容器(无论标准容器还是数组)被销毁时,其元素被销毁
  • 动态内存分配,当对它的指针使用delete时被销毁
  • 对于临时对象,当创建它的完整表达式结束时被销毁
//example6.cpp
class Person

public:
    int age;
    string name;
    Person() = default;
    Person(int age, string name) : age(age), name(name) 
    ~Person()
    
        cout << "~Person" << endl;
    
;

Person func(Person person)

    return person;


int main(int argc, char **argv)

    Person person(19, "me");
    Person person1 = func(person);
    //~Person被打印三次
    //首先将person拷贝给func的形参,然后形参person作为返回值赋值给person1
    //然后func返回值person被销毁
    //随着main执行完毕,main内的两个Person被销毁
    return 0;

合成析构函数

当为自定义析构函数时,编译器会自动提供一个合成析构函数,对于某些类作用为阻止该类型的对象被销毁,如果不是则函数体为空

//example7.cpp
class Person

public:
    int age;
    string name;
    Person() = default;
    Person(int age, string name) : age(age), name(name) 
    ~Person()  //等价于合成析构函数
;

int main(int argc, char **argv)

    Person person(19, "gaowanlu");
    cout << person.age << " " << person.name << endl; // 19 gaowanlu
    return 0;

在合成析构函数体执行完毕之后,成员会被自动销毁,对象中的string被销毁时,将会调用string的析构函数,将name的内存释放掉,析构函数自身并不直接销毁成员,是在析构函数体之后隐含的析构阶段中被销毁的,整个销毁过程,析构函数体是作为成员销毁步骤之外的并一部分而进行的

如果对象的内部有普通指针记录new动态内存,在对象析构过程默认只进行指针变量指针本身的释放,而不对申请的内存进行释放,则就需要动态内存章节学习的在析构函数体内手动释放他们,或者使用智能指针,随着智能指针的析构被执行,动态内存会被释放

三/五法则

有三个基本操作可控制类的拷贝操作:拷贝构造函数、拷贝赋值运算符、析构函数。在新标准下还可以通过定义一个移动构造函数、一个移动赋值运算符
我们发现有时赋值运算符与拷贝构造函数会执行相同的功能,通常情况下并不要求定义所有这些操作

使用合成拷贝函数和合成拷贝赋值运算符时可能遇见的问题

//example8.cpp
class Person

public:
    int age;
    string *name;
    Person(const string &name = string()) : name(new string(name)), age(0) 
    ~Person()
    
        delete name;
    
;

int main(int argc, char **argv)

    
        Person person1("me");
        Person person2 = person1; //使用合成拷贝构造函数
        //此时的person1.name与person2.name指向相同的内存地址
        *person1.name = "he";
        cout << *person2.name << endl; // he
    
    cout << "end" << endl; // end
    return 0;

在合成拷贝构造函数和合成拷贝赋值运算符,其中的拷贝操作都是简单的指针地址赋值,而不是重新开辟空间,再将原先的name赋值到新的内存空间

使用=default

使用=default可以显式要求编译器生成合成拷贝构造函数和拷贝赋值运算符

//example9.cpp
class Person

public:
    Person() = default;                //合成默认构造函数
    Person(const Person &) = default;  //合成拷贝构造函数
    Person &operator=(const Person &); //合成拷贝赋值运算
    ~Person() = default;               //合成析构函数
;

//默认在类内使用=default的成员函数为内联的
//如果不希望是内联函数则应在类外部定义使用=default
Person &Person::operator=(const Person &person) = default;

int main(int argc, char **argv)

    Person person1;
    Person person2 = person1;
    cout << "end" << endl; // endl
    return 0;

=delete阻止拷贝

使用=delete定义删除的函数

//example10.cpp
class Person

public:
    Person() = default;
    Person(const Person &) = delete;            //禁止拷贝构造函数
    Person &operator=(const Person &) = delete; //阻止拷贝赋值
    ~Person() = default;
;

int main(int argc, char **argv)

    Person person1;
    // Person person2 = person1;//错误 不允许拷贝复制赋值
    return 0;

析构函数不能是删除的成员,否则就不能销毁此类型,没有析构函数的类型可以使用动态分配方式创建,但是不能被销毁

//example11.cpp
class Person

public:
    int age;
    string name;
    Person(const int age, const string name) : age(age), name(name) 
    ~Person() = delete;
;

int main(int argc, char **argv)

    Person *person = new Person(19, "me");
    // delete person;//错误 Person没有析构函数
    return 0;

编译器将成员处理为删除的

对于某些情况,编译器会将合成的成员定义为删除的函数

重点:如果一个类有数据成员不能默认构造、拷贝、复制、销毁,则对应的成员函数将被定义为删除的

private拷贝控制

在新标准之前没有,删除的成员,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝的

//example12.cpp
class Person

private:
    Person(const Person &person);
    Person &operator=(const Person &person);

public:
    int age;
    string name;
    Person(const int age, const string name) : age(age), name(name) 
    ~Person() = default;
    Person() = default;
    void test();
;

Person::Person(const Person &person)


Person &Person::operator=(const Person &person)

    return *this;


void Person::test()

    Person *person = new Person(19, "me");
    Person person1 = *person; //函数成员或者友元函数可以使用
    delete person;


int main(int argc, char **argv)

    Person person1(19, "me");
    // Person person2 = person1;
    // error: 'Person::Person(const Person&)' is private within this context
    person1.test();
    return 0;

这种虽然类的外部不能使用拷贝构造和拷贝赋值,但是类的友元和成员函数仍可使用二者,同时想要阻止友元函数或者成员函数的使用,则只声明private成员即可不进行定义

//example13.cpp
class Person

private:
    Person(const Person &person);            //只声明不定义
    Person &operator=(const Person &person); //只声明不定义

public:
    int age;
    string name;
    Person(const int age, const string name) : age(age), name(name) 
    ~Person() = default;
    Person() = default;
    void test();
;

int main(int argc, char **argv)

    Person person1(19, "me");
    // Person person2 = person1;
    //  error: 'Person::Person(const Person&)' is private within this context
    // 如果函数成员或友元函数使用拷贝构造或者赋值 也会报错
    return 0;

总之优先使用=delete这种新的规范,delete是从编译阶段直接解决问题

行为像值的类

有些类拷贝是值操作,是一份相同得副本

//example14.cpp
class Person

public:
    int *age;
    string *name;
    Person(const int &age, const string &name) : age(new int(age)), name(new string(name)) 
    Person() : age(new int(0)), name(new string("")) 
    Person &operator=(const Person &person);
    ~Person()
    
        delete age, delete name;
    
;

Person &Person::operator=(const Person &person)

    *age = *person.age;
    *name = *person.name;
    return *this;


int main(int argc, char **argv)

    Person person1(19, "me");
    Person person2(20, "she");
    person1 = person2;
    cout << *person1.age << " " << *person1.name << endl; // 20 she
    cout << *person2.age << " " << *person2.name << endl; // 20 she
    *person1.name = "gaowanlu";
    cout << *person1.age << " " << *person1.name << endl; // 20 gaowanlu
    cout << *person2.age << " " << *person2.name << endl; // 20 she
    //可见之间此类对象像一种值类型
    return 0;

行为像指针的类

有些类拷贝是指针指向的操作,也就是不同的类的成员会使用相同的内存

先来看一种简单使用的情况

//example15.cpp
class Person

public:
    int *age;
    string *name;
    Person() : age(new int(0)), name(new string) 
    Person(const int &age, const string &name) : age(new int(age)), name(new string(name)) 
    Person &operator=(const Person &person);
;

Person &Person::operator=(const Person &person)

    if (age)
        delete age;
    if (name)
        delete name;
    age = person.age;
    name = person.name;
    return *this;


int main(int argc, char 以上是关于C++拷贝控制技术的主要内容,如果未能解决你的问题,请参考以下文章

浅谈C++类的拷贝控制

拷贝控制C++

拷贝控制C++

C++语言第十三章:拷贝控制

C++中的RAII和拷贝控制

C++ Primer笔记15---chapter13 拷贝控制2