C++类特殊函数
类中的几个特殊函数
A() //构造函数
~A() //析构函数
A(const A&) //复制构造函数
A& operator = (const A &) //复制赋值运算符
A(A&&) //移动构造函数
A& operator=(A&& a)//移动赋值函数
//以上为默认函数
explicit operator bool() //向外转换
通过 = default 和 = delete来控制默认函数
具体的隐式声明情况见
默认构造函数 – 析构函数
复制构造函数 – 复制赋值
移动构造函数 (C++11)
移动赋值 (C++11)
转换构造函数 – explicit 说明符
示例
using namespace std;
#include<iostream>;
#include<initializer_list>
#include<memory>
struct T
{
int n;
T() { cout << "T()" << endl; }
T(int n) : n(n) { cout << "T(int n)" << endl; }
};
构造函数与析构函数
class A
{
T x;
int y;
int* z;
shared_ptr<int> u;
unique_ptr<int> v;
public:
//构造函数
A() :x(0),
y(0),
z(new int(0)),
u(new int(1)),
v(new int(2))
{
cout << "A()" << endl;
}
//析构函数
~A() { cout << "~A()" << endl; delete z; }
//转换函数
A(const int& x, const int& y) : x(x),
y(0),
z(new int(0)),
u(new int(1)),
v(new int(2))
{
cout << "A(const int& x, const int& y)" << endl;
}
调用方式
//默认构造函数
A();
//转换构造函数//自定义构造函数
A a(1, 2);
A a2{ 1,2 }; // modern C++
A a3 = { 1,2 }; //explicit 声明后不能使用
A a4 = A(1, 2); //explicit 声明后不能使用
A&& a9 = A(2, 3);//右值 //explicit 声明后不能使用
A&& a10(A(2, 3));//右值
A&& a11{ 2,3 };//右值
A&& a12(A{ 2,3 });//右值
复制构造函数
//复制构造函数
//形参 T&、const T&、volatile T& 或 const volatile T&
A(const A& a) :
x(a.x),
y(a.y),
z(new int{ *a.z }),//deep copy
u(a.u),//share
v(new int{ *a.v })//deep copy
{
cout << "A(const A& a)" << endl;
}
浅拷贝(默认)
复制所有成员值
深拷贝(自定义)
对于指针成员对象,开辟新的内存并拷贝
调用方式
- 初始化:T a = b; 或 T a(b);,其中 b 的类型是
T; - 函数实参传递:f(a);,其中
a的类型是T而f是 void f(T t); - 函数返回:在像 T f() 这样的函数内部的 return a;,其中
a的类型是T且它没有移动构造函数。
//复制构造函数
A a5(a);
A a6 = a; //explicit 声明后不能使用
复制赋值运算符
//复制赋值运算符
//形参T、T&、const T&、volatile T& 或 const volatile T&
A& operator=(const A& a)
{
cout << "A& operator=(const A& a)" << endl;
x = a.x;
y = a.y;
*z = *a.z;//deep copy
u = a.u;//share
v = make_unique<int>(*a.v);//deep copy
return *this;
}
调用方法
//复制赋值运算符
a5 = a2;
移动构造函数
//移动构造函数//std::move
//形参 T&&、const T&&、volatile T&& 或 const volatile T&&
A(A&& a) :
//类类型成员隐式移动
x(a.x),
//非类类型成员的隐式移动
y(a.y),
///z(a.z) //未删除原指针 多次释放内存
//类类型成员显式移动
//x(std::move(a.x)),
//y(std::move(a.y)),
//非类类型成员的显式移动
//y(std::exchange(a.y, 0))
///指针的显式移动
z(std::exchange(a.z, nullptr)),
u(std::exchange(a.u, nullptr)),
v(std::exchange(a.v, nullptr))
{
cout << "A(const A&& a)" << endl;
}
std::exchange
template< class T, class U = T >
T exchange(T& obj, U&& new_value);//(C++14 起)//(C++20 前)
template< class T, class U = T >
constexpr T exchange(T& obj, U&& new_value);//(C++20 起)//(C++23 前)
template< class T, class U = T >
constexpr T exchange(T& obj, U&& new_value) noexcept;//(C++23 起)
注意指针移动,避免重复释放资源
智能指针同样采用std::exchange
调用
//移动构造函数
A a7(std::move(a));
A a8 = std::move(a2);
区别于右值引用
//右值引用
A&& a13 = std::move(a3);
A&& a14 = (A&&)a4;
左值引用和右值引用
- 左值引用:引用一个对象;
- 右值引用:就是必须绑定到右值的引用,C++11中右值引用可以实现“移动语义”,通过 && 获得右值引用。
int x = 6; // x是左值,6是右值
int &y = x; // 左值引用,y引用x
int &z1 = x * 6; // 错误,x*6是一个右值
const int &z2 = x * 6; // 正确,可以将一个const引用绑定到一个右值
int &&z3 = x * 6; // 正确,右值引用
int &&z4 = x; // 错误,x是一个左值
右值引用和相关的移动语义是C++11标准中引入的最强大的特性之一,通过std::move()可以避免无谓的复制,提高程序性能。
移动赋值运算符
解释
\1) 移动赋值运算符的典型声明。
\2) 强制编译器生成移动赋值运算符。
\3) 避免隐式移动赋值。
每当重载决议选择移动赋值运算符时,它都会被调用,例如当对象出现在赋值表达式左侧,而其右侧是同类型或可隐式转换的类型的右值时
典型的移动赋值运算符“窃取”实参曾保有的资源(例如指向动态分配对象的指针,文件描述符,TCP socket,输入输出流,运行的线程,等等),而非复制它们,并使得实参遗留在某个合法但不确定的状态。
//移动赋值函数
//形参 T&&、const T&&、volatile T&& 或 const volatile T&&
A& operator=(A&& a)
{
cout << "A& operator=(const A&& a)" << endl;
x = a.x;
y = a.y;
z = std::exchange(a.z, nullptr);
u = std::exchange(a.u, nullptr);
v = std::exchange(a.v, nullptr);
return *this;
}
调用
//移动赋值函数
a7 = std::move(a5);
a7 = { 2,3 };//右值//先转换构造一类
a7 = A(2, 3);//右值