C++ noncopyable原理及实现
默认构造函数、拷贝控制成员以及它们的合成版本
默认构造函数
不含参数或者只含一个参数并且参数有默认值的构造函数。
如果类没有定义任何构造函数,编译器会为其合成一个无参默认构造函数,也就是合成的默认构造函数。
如果类定义了构造函数,编译器不会为其合成默认构造函数,也就不能执行默认初始化了,如:
class A {
int val;
A(int v):val(v) {}
};
A a; //编译出错,没有默认构造函数
可以指定编译器合成一个默认构造函数,用default关键字。(只能对具有合成版本的成员函数使用此关键字,如默认构造函数和拷贝控制成员)
class A {
int val;
A(int v):val(v) {}
A() = default;
};
A a; //编译通过
合成的拷贝控制成员
- 合成的拷贝控制成员包括拷贝构造函数和拷贝赋值运算符。
- 如果类没有定义拷贝构造函数,编译器会为其定义一个,即使类定义了构造函数,编译器也会为其合成一个拷贝构造函数。
- 如果类没有定义拷贝赋值运算,编译器会为其定义一个,也就是合成的拷贝赋值运算符。
- 可以用关键字default显示要求编译器合成一个拷贝控制成员。
访问控制和继承
访问控制
- 类的私有成员只能有本类的其他成员和友元来访问。
- 类的受保护成员不可以由类的用户访问,可以由派生类的成员和友元访问。
- 派生类的成员和友元只能通过派生类对象访问基类的受保护成员,不能直接访问基类对象的受保护成员。
如:
class Base {
protected:
int val;
};
class Derived:public Base{
friend void f1(Base&);
friend void f2(Derived&);
};
void f1(Base &b) {b.val = 10;} //错误,不能通过基类对象访问
void f2(Derived &d) {d.val = 10;} //正确,可以通过派生类对象访问基类部分的protected
公有、私有和受保护继承
某个类对继承而来的成员的访问权限受两个因素影响:
一是基类中该成员的访问说明符;
二是派生类的派生列表中的访问说明符。
派生列表中的访问控制说明符对派生类的成员和友元能否访问其直接基类的成员没有影响,对基类成员访问只与基类中访问说明符有关。
class Base {
public:
void pub_mem();
protected:
int prot_mem;
private:
int priv_mem;
};
struct Pub_Derv : public Base {
int f() { return prot_mem; } //正确,可以访问
int g() { return priv_mem; } //错误,不可访问私有成员
};
struct Priv_Derv : private Base {
int f() { return prot_mem; } //正确,不影响访问
};
int main(){
Pub_Derv pub;
Priv_Derv priv;
pub.pub_mem(); //正确,public继承 用户(包括派生类的派生类)可以直接访问类的基类部分的共有成员
priv.pub_mem(); //错误,private继承 用户不能访问类的基类部分的任何成员
}
noncopyable原理及实现
class noncopyable的基本思想是把构造函数和析构函数设置protected权限,这样子类可以调用,但是外面的类不能调用,那么当子类需要定义构造函数的时候不至于通不过编译。但是最关键的是noncopyable把拷贝构造函数和拷贝赋值函数做成了private的,继承自noncopyable的类在执行拷贝操作时会调用基类的拷贝操作,但是基类的拷贝操作是private的,因此无法调用,引发编译错误。
class noncopyable
{
public:
noncopyable(const noncopyable&) = delete;//拷贝构造禁用
void operator=(const noncopyable&) = delete;//赋值禁用
protected:
noncopyable() = default;
~noncopyable() = default;
};
noncopyable有啥优势
对象是否可复制,与他是否使用RAII技巧无关,却与它 本身是值语义还是引用语义相关
值语义:关心这个对象表示的值。在复制的时候,类似使用普通的int。使用的是其值。复制前后的对象没有关系。
引用语义:关心对象本身,复制一个具有引用语义的对象通常是没有意义的。例如复制一个线程,理论上需要完全创建一个相同的线程才行,所以不允许复制。
如果不清楚某个类是否应该复制,默认可以写成不可复制,必要时修改为可复制,通常这是一个不错的做法。