类的拷贝、赋值与销毁

拷贝构造函数、拷贝赋值运算符

移动构造函数、移动赋值运算符

析构函数

当定义一个类时,我们显式和隐式地指定在此类型对象的拷贝、移动、赋值和销毁时做些什么。而一个类通过定义上述五种特殊的成员函数来控制这些操作。

拷贝和移动构造函数定义了当同类型的另一个对象初始化本对象时做什么。

拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。

析构函数定义了当此类型对象销毁时做什么。

1、拷贝构造函数

class Foo {
    public:
        Foo();  //默认构造函数
        Foo(const Foo&); //拷贝构造函数
};
拷贝构造函数的第一个参数是引用类型,而且此参数几乎总是一个const的引用。

合成拷贝构造函数(synthesized copy constructor)

如果我们没有定义拷贝构造函数,编译器则会为我们定义一个合成拷贝构造函数。

一般情况下,合成拷贝构造函数将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。

每个成员的类型决定其如何拷贝:

类类型的成员---------调用其拷贝构造函数来拷贝

内置类型-------------直接拷贝

拷贝初始化

拷贝初始化和直接初始化的差异

string dots(10, ' ');   //直接初始化
string s(dots);         //直接初始化
string s2 = dots;      //拷贝初始化
string book = "9-999-99999-9";   //拷贝初始化
string nines = string(100, '9');     //拷贝初始化
当使用直接初始化时,我们实际上是要求编译器使用普通的函数匹配来选择与我们提供参数最匹配的构造函数。

而使用构造初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。

拷贝初始化通常使用拷贝构造函数完成,有时也使用移动构造函数完成。

拷贝初始化除了在我们用 = 定义变量时会发生,在下列情况也会发生

  1. 将一个对象作为实参传递给一个非引用类型的形参 
  2. 从一个返回类型为非引用类型的函数返回一个对象
  3. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员

例如:在函数调用过程中i,具有非引用类型的参数要进行拷贝初始化,类似的,当一个函数返回具有非引用的返回类型时也会。

2、拷贝赋值运算符

与类控制其对象如何初始化一样,类也可以控制其对象如何赋值。

Sales_data trans, accum;
trans = accum;    //使用Sales_data的拷贝赋值运算符
合成拷贝赋值运算符
如果一个类未定义自己的拷贝赋值运算符,编译器会自动为其生成一个合成拷贝赋值运算符。它会将右侧运算对象的每一个非static成员赋予左侧运算对象。

合成拷贝赋值运算符返回一个指向其左侧运算对象的引用。

//等价于合成拷贝赋值运算符
Sales_data&
Sales_data::operator=(const Sales_data &rhs) {
    bookNO = rhs.bookNO;          //调用string::operator=
    units_sold = rhs.units_sold;  //使用内置的int赋值
    revenue = rhs.revenue;  
    return *this;            //返回一个此对象的引用
}

3、析构函数

析构函数执行与构造函数相反的操作:构造函数初始化对象的非static数据成员,还可能做一些其他的工作:析构函数释放对象使用的资源,并销毁对象的非static数据成员。

如同构造函数有一个初始化部分和一个函数体,析构函数也有一个函数体和一个析构部分。在一个构造函数中,成员的初始化是在函数体之前完成的,且按照它们在类中出现的顺序初始化。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化的顺序逆序销毁。

无论何时一个对象被销毁,就会自动调用其析构函数:

  1. 变量在离开其作用域时被销毁
  2. 当一个对象被销毁时,其成员被销毁
  3. 容器(无论是标准库容器还是数组)被销毁时,其元素被销毁
  4. 对于动态分配的对象,当指向它的指针应用delete运算符时被销毁
  5. 对于临时对象,当创建它的完整表达式结束时被销毁

合成析构函数

当一个类未定义自己的析构函数时,编译器会自动为它分配一个合成的析构函数

class Sales_data {
    public:
        //成员会被自动销毁,除此之外不需要做其他事情
        ~Sales_data() { }
        //其他成员的定义,如前
};

认识到析构函数体自身并不直接销毁成员是非常重要的。成员是在析构函数体之后隐含的析构阶段中被销毁的。在整个对象销毁的过程中,析构函数体是作为成员销毁步骤之外的另一部分进行的。

4、三五法则

  1. 需要析构函数的类也需要拷贝和赋值操作
  2. 需要拷贝操作的类也需要赋值操作,反之亦然

5、使用 = default

我们通过将拷贝控制成员定义为 =default 来显式地要求编译器生成合成的版本。

class Sales_data {
public:
    //拷贝控制成员,使用 =default
    Sales_data() = default;
    Sales_data(const Sales_data &) = default;
    Sales_data&operator=(const Sales_data &);
    ~Sales_data() = default;
    //其他成员定义,如前
};
Sales_data& Sales_data::operator=(const Sales_data &) = default;

6、阻止拷贝

对于某些类来说,拷贝操作没有合理的意义,在此情况下,定义类时必须采用某种机制阻止拷贝或赋值。例如,iostream类阻止了拷贝,以避免多个对象写入或读取相同的IO缓冲。阻止拷贝,除了不定义拷贝控制成员外,还要考虑编译器合成的版本。

定义删除的函数

在新标准下,我们通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。

struct NoCopy {
    NoCopy() = default;    //使用合成的默认构造函数
    NoCopy(const NoCopy&) = delete;   //阻止拷贝
    NoCopy &operator=(const NoCopy&) = delete;   //阻止赋值
    ~NoCopy() = default;
    //其他成员
};

析构函数不能是删除的成员

值得注意的是,我们不能删除析构函数。如果析构函数被删除,就无法销毁此类型的对象了。对于一个删除了析构函数的类型,编译器不允许定义该类型的变量或创建该类型的对象。而且,如果一个类有一个成员的类型删除了析构函数,我们也不能定义该类的变量或临时对象。

合成的拷贝控制成员可能是删除的

对下列情况的类,编译器将合成的成员定义为删除的函数:

  1. 如果类的某个成员的析构函数是删除的或不可访问的(例如,是private的),则类的合成析构函数定义为删除的。
  2. 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成构造函数定义为删除的。如果类的某个成员的析构函数是删除的或不可访问的,则类的合成构造函数定义为删除的。
  3. 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或类有一个const的或引用成员,则类的合成拷贝赋值运算符定义为删除的。
  4. 如果类的某个成员的析构函数是删除的或不可访问的,或类有一个引用成员,它没有类内初始化器,或类有一个const成员,它没有类内初始化器且类型未显式定义默认构造函数,则类的默认构造函数定义为删除的。

本质上,这些规则的含义是,如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。

private拷贝控制

在新标准发布以前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝的。

<think>我们正在讨论Qt的创建、赋值销毁。根据引用内容,我们可以总结如下: 1. 创建:在Qt中,对象的创建通常使用`new`操作符,并且可以通过构造函数指定父对象(如引用[4]所示)。当指定父对象后,对象会被添加到父对象的对象树中,由父对象负责管理其生命周期(引用[3]提到Qt使用对象树管理QObject及其子对象)。 2. 赋值:在C++中,对象的赋值通常通过拷贝赋值运算符(`operator=`)完成。但是,在Qt中,很多(尤其是QObject及其子)禁止拷贝构造拷贝赋值(引用[1]提到“阻止拷贝“对象移动”相关内容)。因此,对于QObject子,我们通常使用指针进行操作,避免直接拷贝对象。 3. 销毁:Qt中,当父对象被销毁时,它会自动销毁其所有子对象(引用[3])。这是通过Qt的对象树机制实现的。因此,一般情况下,我们不需要手动删除子对象(除非在特定情况下,比如没有父对象的对象,需要手动管理)。 下面我们详细展开: ### Qt对象的创建 在Qt中,创建对象通常使用`new`操作符,并可以指定父对象。例如,创建一个按钮并指定父对象为当前窗口: ```cpp // 在窗口的构造函数中 QPushButton *button = new QPushButton("登录", this); // 指定父对象为this(当前窗口) ``` 这样,按钮就会成为窗口的一个子部件,当窗口销毁时,按钮也会自动销毁。 ### Qt对象的赋值 由于QObject及其子禁止拷贝构造拷贝赋值(通过将拷贝构造函数赋值运算符声明为`private`或`=delete`),因此我们不能直接拷贝一个QObject子对象。例如,以下代码是错误的: ```cpp QPushButton button1("按钮1"); QPushButton button2 = button1; // 错误:拷贝构造函数是私有的 ``` 如果需要“赋值”操作,我们通常通过复制对象的状态(属性)来实现,或者使用克隆方法(如果该提供了克隆接口)。另一种方式是使用指针,通过改变指针指向的对象来间接实现“赋值”。 ### Qt对象的销毁 Qt对象的销毁主要通过对象树机制自动完成:当一个父对象被销毁时,它会自动销毁其所有子对象。我们也可以手动删除一个对象(使用`delete`操作符),但前提是该对象没有父对象或者已经从父对象中移除。 例如,手动删除一个按钮对象: ```cpp delete button; ``` 注意:如果该按钮有父对象,通常不需要手动删除,因为父对象销毁时会自动删除它。如果手动删除一个已经被父对象删除的对象(比如在父对象销毁后再次手动删除),会导致程序崩溃。 ### 注意事项 - 对于非QObject的Qt(如QString, QList等),它们是可以拷贝的,并且使用值语义。这些拷贝时通常采用隐式共享(写时复制)机制,以提高效率。 - 当使用对象树管理对象时,避免循环引用(例如,两个对象互相设置为父对象)会导致内存泄漏。 ### 相关问题 1. Qt中,如何确保对象在正确的时机被销毁? 2. 为什么Qt禁止QObject的拷贝构造拷贝赋值? 3. 在Qt中,如何复制一个QObject子对象的信息? [^1]: C++学习笔记(拷贝赋值销毁) [^3]: Qt | 对象树生命期(对象的创建、销毁、擦查找) [^4]: QT按钮的创建属性设置 </think>### Qt的创建、赋值销毁方法 #### 1. **创建对象** 在Qt中,对象创建通常通过`new`操作符完成,需注意父子对象关系: ```cpp // 示例:创建QPushButton对象 QPushButton* button = new QPushButton("登录", this); // 指定父对象 ``` **关键点**: - 指定父对象(如`this`)时,对象会被纳入**Qt对象树**管理 - 未指定父对象需手动管理生命周期 - 构造函数可同时初始化属性(如按钮文本)[^4] #### 2. **赋值操作** Qt对象赋值需注意: ```cpp // 示例:修改按钮属性(非直接对象赋值) button->setText("退出"); // 通过成员函数修改状态 ``` **限制**: - `QObject`及其子**禁止拷贝构造赋值** - 赋值操作需通过: - 单独修改属性(如`setText()`) - 复制指针而非对象本身 - 使用`QSharedPointer`等智能指针[^1][^3] #### 3. **销毁对象** 销毁机制基于对象树: ```cpp // 父对象销毁时自动销毁子对象 delete parentWidget; // 自动递归销毁所有子对象 ``` **规则**: - 父对象销毁 → 自动销毁所有子对象 - 无父对象需手动`delete` - 析构函数自动触发(无需显式调用)[^3][^5] #### 4. **特殊场景处理 - **阻止拷贝**:通过`Q_DISABLE_COPY(ClassName)`宏禁用拷贝 - **移动语义**:C++11后支持移动构造函数(`std::move`) - **静态方法**:`static`方法不依赖对象实例[^2] #### 生命周期示意图 ``` 创建(new) → 操作(setText等) → 销毁(delete) ↑ | 父对象析构时自动触发 ``` --- ### 相关问题 1. Qt对象树如何避免内存泄漏?[^3] 2. 为什么Qt禁止`QObject`子拷贝操作?[^1] 3. 如何在Qt中安全传递对象所有权?[^5] 4. 静态方法(`static`)非静态方法在Qt中的使用区别?[^2] [^1]: C++学习笔记(拷贝赋值销毁) [^2]: QT学习积累——方法加static不加static的区别 [^3]: Qt | 对象树生命期(对象的创建、销毁、擦查找) [^4]: QT按钮的创建属性设置 [^5]: 【C++ 包裹 std::thread】探索C++11 std::thread
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值