CS106L Lecture 14: Special Member Functions

概述

image

class Widget{
  Widget(); //默认构造函数
  Widget(const Widget& w); /拷贝构造函数
  Widget& operator =  (const Widget& w); //拷贝赋值运算符
  ~Widget();  //析构函数
  Widget(Widget&& rhs); //移动构造函数
  Widget& operator = (const&& rhs); //移动赋值运算符
};

Widget();

不接收参数构造新类

Widget(const Widget& w);

创建一个新对象,作为另一个对象的成员级副本

Widget widgetOne;
Widget widgetTwo = widgetOne;//copy constructor is called

Widget& operator = (const Widget& w);

将一个已经存在的对象分配给另一个

Widget widgetOne;
Widget widgetTwo;
widgetTwo = widgetOne;

~Widget();

当对象超出范围时调用

我们不必写出这些!它们都有默认版本,可以自动生成!

Copy and copy assignment

introducting

template <typename T>
Vector<T>::Vector()
{
 _size = 0;
 _capacity = 4;
 _data = new T[_capacity];
}

这个初始化是低效的,它分为两步:成员变量先默认初始化,再被赋值
我们可以使用初始化列表来声明并同时用期望的值初始化它们!

template <typename T>
Vector<T>::Vector() : _size(0), _capacity(4), _data(new
T[_capacity]) { }

如果变量是不可分配的类型怎么办?

C++ 里,const 成员和引用成员(如代码里的 _constant 和 _reference )属于非赋值类型(non - assignable types) ,原因如下:

const 成员:const 修饰的变量一旦初始化,就不能再被赋值。要是在构造函数函数体里写 _constant = value; ,这属于赋值操作,会编译报错,所以必须用初始化列表在创建对象时直接初始化 。
引用成员:引用必须在初始化时就绑定到一个对象,之后无法再重新绑定(即不能赋值改变引用指向)。若在构造函数函数体里尝试给引用成员赋值,不是合法的 “绑定” 操作,会编译报错,因此得用初始化列表在构造对象时完成引用绑定 。

对于 const 和引用这类非赋值类型的成员,只能通过构造函数初始化列表来初始化,没法在构造函数函数体里用赋值方式处理,这就是代码必须用初始化列表的缘由。

template <typename T>
class MyClass {
const int _constant;
int& _reference;
public:
// Only way to initialize const and reference members
MyClass(int value, int& ref) : _constant(value),
_reference(ref) { }
};

考虑指针

template <typename T>
Vector<T>::Vector<T>(const Vector::Vector<T>& other) :
_size(other._size), _capacity(other._capacity),
_data(other._data) { };//这些指针将指向同一个底层数组!

核心问题:代码里对 Vector 类拷贝构造函数的实现是浅拷贝,_data 指针直接复制地址,导致两个对象的 _data 指向同一块内存。对其中一个对象通过指针操作数据,会影响另一个对象,还可能因重复释放内存引发崩溃。
解决思路:若要让拷贝出的对象完全独立,需实现深拷贝,即在拷贝构造函数(及赋值运算符重载等)里,为新对象的 _data 重新分配内存,再把原数据逐个拷贝过去,而非直接复制指针。这样新、旧对象的 _data 指向不同内存,操作彼此独立 。
关键概念:默认的拷贝操作(如这里的浅拷贝)仅简单复制成员变量,对于含指针等资源的类,往往需手动重写拷贝构造函数、赋值运算符等特殊成员函数,来正确管理资源,避免问题。

Vector<T>::Vector(const Vector<T>& other)
: _size(other._size), _capacity(other._capacity), _data(new
T[other._capacity]) {
for (size_t i = 0; i < _size; ++i) {
_data[i] = other._data[i];
  }
}

delete

作用:通过 = delete 可以删除拷贝构造函数、拷贝赋值运算符等特殊成员函数,阻止对象被拷贝,让拷贝操作无法进行(如 PasswordManager 类,删除后无法用拷贝构造、赋值来复制对象 )。
应用场景:像单例模式(限制只有一个实例),或者 std::unique_ptr (确保指针唯一所有权,禁止拷贝,支持移动)这类场景,需要限制对象拷贝时,就可删除这些特殊成员函数 。
效果:删除后,若代码里尝试拷贝对象(如用 = 赋值、拷贝构造新对象),编译器会报错,从而达到 “选择性禁用” 特定特殊成员函数功能的目的,精准控制类的对象复制行为 。
image

Rule of Zero

核心思想:若类的默认特殊成员函数(构造、析构、拷贝赋值等)能满足需求,就别手动定义。若类依赖的成员对象(如 std::string )自身已实现完善的特殊成员函数,无需重复编写相关逻辑,借助成员对象的默认行为即可。
举例说明:a_string_with_an_id 类里,int 和 std::string 都有编译器生成或自身已实现的特殊成员函数(std::string 更是完善),所以该类无需额外定义构造、拷贝赋值等,依靠默认生成的就能正确工作 。
价值:遵循零规则可减少冗余代码,利用已有类型(如标准库类型)的资源管理逻辑,降低出错风险,让代码更简洁、易维护
image

Rule of Three

核心是若类需自定义析构函数(常因手动管理动态内存,如释放堆内存 ),通常也得自定义拷贝构造函数和拷贝赋值运算符。
原因:手动内存管理时,编译器默认生成的拷贝操作是浅拷贝,会导致多个对象共享同一块动态内存,析构时重复释放等问题,所以需自定义这三个函数来正确管理资源,保证拷贝后对象独立、内存安全 。

image
场景:StringTable 类用拷贝构造函数拷贝对象时,因要逐个复制 std::map 里的元素,若 map 数据量大,会逐一枚举、拷贝键值对,耗时久、效率低,造成资源浪费(“wasteful” )。
优化方向:可通过移动语义(Move Semantics) ,结合移动构造函数、移动赋值运算符,转移资源所有权而非深拷贝,提升性能,避免这种低效拷贝。

posted @ 2025-08-01 18:07  Civilight~Eterna  阅读(8)  评论(0)    收藏  举报