C++11中新的类功能

请添加图片描述


前言

在C++11中,扩充了许多“新的类功能”,其中涵盖了 默认移动构造/移动赋值、成员变量缺省值、default/delete 关键字、final/override 四大核心特性,他们既解决了传统类中深拷贝效率低和默认函数控制不便的缺点,又规范了继承多态中的语法细节,是现代C++类设计的基础。


1.默认的移动构造和移动赋值

1.1.核心概念与设计初衷

在C++11以前的类中,有6个默认成员函数(不写会自动生成的函数):构造函数/析构函数/拷贝构造函数/拷贝赋值函数/取地址重载/const取地址重载,其中最重要的是前4个。

C++11中新增了两个默认成员函数:移动构造函数和移动赋值运算符重载。

  • 移动构造/移动赋值:接收右值引用参数的特殊成员函数,目的是“窃取”右值资源,避免拷贝
  • 设计初衷:优化深拷贝类(如string、vector等)的传值返回和容器插入等场景,减少内存拷贝与释放的开销。

1.2.编译器自动生成规则

  • 如果我们没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造函数。

默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现了移动构造,如果实现了就调用移动构造,没有就调用拷贝构造。

  • 如果我们没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值重载函数。

默认生成的移动赋值重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现了移动赋值,如果实现了就调用移动赋值,没有就调用拷贝赋值。

  • 如果自己实现了移动构造或移动赋值重载,则编译器就不会自动提供拷贝构造和拷贝赋值。

1.3.手动实现与使用示例

代码示例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstring>
#include <utility> // 用于 std::move
using namespace std;

// 场景1:编译器自动生成默认移动构造和移动赋值
// 条件:未手动实现移动构造/移动赋值,且未实现析构、拷贝构造、拷贝赋值
class AutoGenMove {
public:
    // 仅实现普通构造(无析构、拷贝构造、拷贝赋值)
    AutoGenMove(const char* str = "")
        : _size(strlen(str)) {
        _str = new char[_size + 1];
        strcpy(_str, str);
        cout << "AutoGenMove 普通构造" << endl;
    }

    // 成员变量(内置类型+自定义类型,验证不同成员的移动行为)
    char* _str;
    size_t _size;
    string _s; // string已实现移动构造/赋值,用于验证自定义类型成员行为
};

// 场景2:手动实现移动构造/移动赋值,编译器禁止自动生成拷贝构造/赋值
class ManualMove {
public:
    ManualMove(const char* str = "")
        : _size(strlen(str)) {
        _str = new char[_size + 1];
        strcpy(_str, str);
        cout << "ManualMove 普通构造" << endl;
    }

    // 手动实现移动构造(窃取右值资源)
    ManualMove(ManualMove&& other)
        : _str(other._str)
        , _size(other._size)
        , _s(move(other._s)) { // 自定义类型成员调用其移动构造
        other._str = nullptr; // 置空源对象,避免析构重复释放
        other._size = 0;
        cout << "ManualMove 移动构造" << endl;
    }

    // 手动实现移动赋值(窃取右值资源)
    ManualMove& operator=(ManualMove&& other) {
        if (this != &other) {
            // 释放当前对象资源
            delete[] _str;
            // 窃取源对象资源
            _str = other._str;
            _size = other._size;
            _s = move(other._s); // 自定义类型成员调用其移动赋值
            // 置空源对象
            other._str = nullptr;
            other._size = 0;
        }
        cout << "ManualMove 移动赋值" << endl;
        return *this;
    }

    // 析构函数(释放动态内存)
    ~ManualMove() {
        delete[] _str;
        cout << "ManualMove 析构" << endl;
    }

private:
    char* _str;
    size_t _size;
    string _s;
};

// 场景3:实现析构函数,编译器不自动生成移动构造/赋值
class NoAutoMove {
public:
    NoAutoMove(const char* str = "")
        : _size(strlen(str)) {
        _str = new char[_size + 1];
        strcpy(_str, str);
        cout << "NoAutoMove 普通构造" << endl;
    }

    // 实现析构函数(触发规则:编译器不再自动生成移动构造/赋值)
    ~NoAutoMove() {
        delete[] _str;
        cout << "NoAutoMove 析构" << endl;
    }

private:
    char* _str;
    size_t _size;
};

int main() {
    // 测试场景1:编译器自动生成移动构造/赋值
    cout << "=== 场景1:自动生成移动构造/赋值 ===" << endl;
    AutoGenMove a1("auto1");
    AutoGenMove a2 = move(a1); // 调用编译器生成的移动构造(a1._str被窃取)
    AutoGenMove a3;
    a3 = move(a2); // 调用编译器生成的移动赋值(a2._str被窃取)
    // 验证:a1._str已被置空(编译器生成的移动构造会逐字节拷贝指针,需手动置空?不,内置类型仅拷贝值,源对象指针仍指向原内存!)
    // 注意:编译器生成的移动构造对内置指针仅拷贝地址,可能导致double free,需手动实现移动构造处理!
    cout << "a1._str: " << (a1._str ? a1._str : "nullptr") << endl; // 此处a1._str仍指向原内存,存在风险(需手动实现移动构造优化)

    // 测试场景2:手动实现移动构造/赋值,禁止自动生成拷贝构造/赋值
    cout << "\n=== 场景2:手动实现移动,禁止拷贝 ===" << endl;
    ManualMove m1("manual1");
    ManualMove m2 = move(m1); // 调用手动实现的移动构造(m1._str被置空)
    ManualMove m3;
    m3 = move(m2); // 调用手动实现的移动赋值(m2._str被置空)
    // ManualMove m4 = m3; // 编译报错:编译器未自动生成拷贝构造(手动实现移动后禁止)
    // m3 = m2; // 编译报错:编译器未自动生成拷贝赋值(手动实现移动后禁止)

    // 测试场景3:实现析构,不自动生成移动构造/赋值
    cout << "\n=== 场景3:实现析构,无自动移动 ===" << endl;
    NoAutoMove n1("noauto1");
    // NoAutoMove n2 = move(n1); // 编译报错:编译器未自动生成移动构造(实现析构触发规则)
    // NoAutoMove n3;
    // n3 = move(n1); // 编译报错:编译器未自动生成移动赋值(实现析构触发规则)

    return 0;
}

测试结果:
在这里插入图片描述


2.成员变量声明时给缺省值

2.1.语法规则与作用机制

  • 声明语法:类内成员变量声明时,直接赋值,仅为初始化提供缺省值
class A{
//...
private:
	int _a = 1;
	//...
};
  • 生效场景:未在构造函数初始化列表显式初始化时,使用该缺省值初始化成员变量

2.2.优势与使用场景

  • 简化代码:减少构造函数重载
  • 统一默认值:集中管理成员变量默认值,便于维护
  • 代码示例:
    传统缺省值方式:
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    string _name;
    int _age;
    string _gender;

    // 全参数构造
    Person(string name, int age, string gender)
        : _name(name), _age(age), _gender(gender) {}

    // 缺省gender的构造(复用全参构造)
    Person(string name, int age)
        : Person(name, age, "未知") {}

    // 缺省age和gender的构造(复用上面的构造)
    Person(string name)
        : Person(name, 0, "未知") {}

    // 无参构造(复用上面的构造)
    Person()
        : Person("", 0, "未知") {}
};

int main() {
    Person p1;                  // 无参:name="", age=0, gender="未知"
    Person p2("张三");          // 仅name:age=0, gender="未知"
    Person p3("李四", 20);      // name+age:gender="未知"
    Person p4("王五", 25, "男"); // 全参数
    return 0;
}

C++11缺省值方式:

#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    // 成员变量直接声明缺省值(集中管理默认值)
    string _name = "";
    int _age = 0;
    string _gender = "未知";

    // 仅需一个构造函数,参数带默认值即可
    Person(string name = "", int age = 0, string gender = "未知")
        : _name(name), _age(age), _gender(gender) {}
};

int main() {
    Person p1;                  // 无参:使用所有缺省值
    Person p2("张三");          // 仅传name:其他用缺省值
    Person p3("李四", 20);      // 传name+age:gender用缺省值
    Person p4("王五", 25, "男"); // 全参数:覆盖缺省值
    return 0;
}

核心差异对比:

特点传统构造函数重载成员缺省值
代码量需要多个重载函数,冗余仅需一个构造函数,简洁
默认值维护分散在多个构造函数,容易出错集中在成员声明处,便于修改
扩展性新增成员需修改所有相关构造函数新增成员仅需添加缺省值和参数

2.3.与初始化列表的优先级

  • 构造函数初始化列表 > 构造函数体内赋值 > 构造函数参数默认值 > 成员变量声明时的缺省值。
  • 注意:const成员、引用成员需通过初始化列表显式初始化,不可以用声明时缺省值

3.default和delete关键字

  • C++11可以让我们更好的控制要使用的默认函数。假设我们要使用某个默认函数,但它没有默认生成。比如:我们提供了拷贝构造,那么移动构造就不会自动生成了,此时我们可以用default关键字显式指定移动构造生成
  • 在C++98中,如果想要限制某个默认函数的生成,那么可以将这个函数设置成private,并且只声明不定义,这样就禁止了外部的直接调用;在C++11中,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

代码示例:

#include <iostream>
#include <utility>
using namespace std;

// 示例1:C++11中用default显式生成默认函数
class UseDefault {
public:
    // 手动实现拷贝构造(此时编译器不会自动生成移动构造/赋值)
    UseDefault(const UseDefault& other)
        : _data(other._data) {
        cout << "UseDefault 拷贝构造" << endl;
    }

    // 用default显式要求编译器生成默认移动构造
    UseDefault(UseDefault&&) = default;

    // 用default显式要求编译器生成默认移动赋值
    UseDefault& operator=(UseDefault&&) = default;

    // 普通构造与析构
    UseDefault(int data = 0) : _data(data) {}
    ~UseDefault() = default; // 显式生成默认析构

    int _data;
};

// 示例2:C++98禁止默认函数的方式(私有化+只声明不定义)
class Cpp98NoCopy {
private:
    // 只声明不定义,且设为私有
    Cpp98NoCopy(const Cpp98NoCopy&);
    Cpp98NoCopy& operator=(const Cpp98NoCopy&);
public:
    Cpp98NoCopy() { cout << "Cpp98NoCopy 构造" << endl; }
};

// 示例3:C++11用delete禁止默认函数的方式
class Cpp11NoCopy {
public:
    Cpp11NoCopy() { cout << "Cpp11NoCopy 构造" << endl; }

    // 用delete明确禁止生成拷贝构造和拷贝赋值
    Cpp11NoCopy(const Cpp11NoCopy&) = delete;
    Cpp11NoCopy& operator=(const Cpp11NoCopy&) = delete;

    // delete也可用于禁止普通函数的特定重载
    void func(int) {}
    void func(double) = delete; // 禁止double参数的调用
};

int main() {
    // 测试default:手动实现拷贝构造后,仍可通过default获得移动构造
    cout << "=== 测试default ===" << endl;
    UseDefault u1(10);
    UseDefault u2 = move(u1); // 调用编译器生成的默认移动构造
    UseDefault u3;
    u3 = move(u2);           // 调用编译器生成的默认移动赋值

    // 测试C++98禁止拷贝
    cout << "\n=== 测试C++98禁止拷贝 ===" << endl;
    Cpp98NoCopy c98_1;
    // Cpp98NoCopy c98_2(c98_1); // 编译报错:无法访问私有拷贝构造

    // 测试C++11禁止拷贝
    cout << "\n=== 测试C++11禁止拷贝 ===" << endl;
    Cpp11NoCopy c11_1;
    // Cpp11NoCopy c11_2(c11_1); // 编译报错:使用了已删除的函数
    // c11_2 = c11_1;           // 编译报错:使用了已删除的函数

    // 测试delete禁止普通函数重载
    Cpp11NoCopy c11;
    c11.func(10);       // 正常调用int版本
    // c11.func(3.14);   // 编译报错:调用了已删除的函数

    return 0;
}

测试结果:
在这里插入图片描述


4.final与override关键字

4.1.final:限制继承与重写

  • 修饰类:该类不可被继承
// 用final修饰Base类,使其不可被继承
class Base final {
public:
    void show() {
        cout << "这是不可继承的Base类" << endl;
    }
};

// 尝试继承final修饰的类会编译报错
// class Derived : public Base {  // 错误:无法从最终类“Base”派生
// public:
//     void show() {
//         cout << "尝试继承Base类" << endl;
//     }
// };
  • 修饰虚函数:该函数(Function)不可被派生类重写
class Base {
public:
    // 用final修饰虚函数func,使其不可被派生类重写
    virtual void func() final {
        cout << "Base类中不可重写的func()" << endl;
    }

    // 普通虚函数,允许被重写
    virtual void other() {
        cout << "Base类中可重写的other()" << endl;
    }
};

class Derived : public Base {
public:
    // 尝试重写final修饰的虚函数会编译报错
    // void func() override {  // 错误:无法重写最终函数“Base::func”
    //     cout << "尝试重写func()" << endl;
    // }

    // 可以正常重写非final的虚函数
    void other() override {
        cout << "Derived类中重写的other()" << endl;
    }
};

4.2.override:确保虚函数重写正确性

  • 语法格式:派生类重写虚函数时,尾部添加override关键字即可
  • 核心作用:编译时检查重写规则(函数名、参数列表、返回值类型需与基类完全一致),避免隐式错误
  • 代码示例:
#include <iostream>
using namespace std;

class Base {
public:
    // 基类虚函数:参数为int,无返回值
    virtual void print(int num) {
        cout << "Base::print(int): " << num << endl;
    }

    // 基类虚函数:无参数
    virtual void display() {
        cout << "Base::display()" << endl;
    }
};

class Derived : public Base {
public:
    // 正确示例:完全匹配基类虚函数(参数、函数名一致),添加override
    void print(int num) override {
        cout << "Derived::print(int): " << num << endl;
    }

    // 错误示例1:参数类型不匹配(基类是int,此处是double)
    // void print(double num) override {  // 编译报错:与基类虚函数参数列表不匹配
    //     cout << "Derived::print(double): " << num << endl;
    // }

    // 错误示例2:函数名拼写错误(基类是display,此处是disply)
    // void disply() override {  // 编译报错:基类中无“disply”虚函数
    //     cout << "Derived::disply()" << endl;
    // }

    // 错误示例3:重写非虚函数(若基类display不是virtual)
    // 注:此处基类display是virtual,若去掉virtual,下面代码会报错
    // void display() override {  // 编译报错:无法重写非虚函数
    //     cout << "Derived::display()" << endl;
    // }
};

int main() {
    Base* ptr = new Derived();
    ptr->print(100);   // 调用Derived的print(正确重写)
    ptr->display();    // 调用Base的display(未被重写)
    delete ptr;
    return 0;
}

5.注意事项

5.1.移动语义

  • 不是所有的类都需要手动实现移动构造/赋值(浅拷贝类不需要,编译器生成的足矣)

5.2.default/delete

  • delete修饰的函数禁止所有调用(包括友元)
  • 析构函数不可用delete,否则对象将无法析构

5.3.成员变量缺省值

  • 类内缺省值不是初始化,实际初始化在构造函数执行时
  • 静态成员变量的初始值需在类外定义

5.4.final/override

  • override仅能用于虚函数的重写,不能修饰非虚函数
  • final修饰的类可以初始化仅用于禁止继承

总结

本文详细探讨了 C++11 标准为类机制带来的四项核心增强:默认移动语义、成员变量缺省值、default/delete 关键字以及 final/override 关键字。这些特性共同构成了现代C++类设计的基础,解决了传统C++存在的效率、代码冗余以及类型安全等问题。掌握以上内容,能帮助我们编写更高效、更简洁、更安全且更易于维护的现代C++代码。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值