只能在堆上创建对象,也就是只能通过new操作符创建对象,方式如下:
代码如下:
class HeapOnly
{
public:
//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
//1、将构造函数设置为私有
HeapOnly()
{}
//3、将拷贝构造函数设置为私有,并且只声明不实现
//C++98
HeapOnly(const HeapOnly&);
//C++11
//HeapOnly(const HeapOnly&) = delete;
};
说明一下:
方法一
方式如下:
代码如下:
class StackOnly
{
public:
//2、提供一个获取对象的接口,并且该接口必须设置为静态成员函数
static StackOnly CreateObj()
{
return StackOnly();
}
private:
//1、将构造函数设置为私有
StackOnly()
{}
};
但该方法有一个缺陷就是,无法防止外部调用拷贝构造函数创建对象。
StackOnly obj1 = StackOnly::CreateObj();
static StackOnly obj2(obj1); //在静态区拷贝构造对象
StackOnly* ptr = new StackOnly(obj1); //在堆上拷贝构造对象
但是我们不能将构造函数设置为私有,也不能用=delete
的方式将拷贝构造函数删除,因为CreateObj函数当中创建的是局部对象,返回局部对象的过程中势必需要调用拷贝构造函数。
方法二
方式如下:
代码如下:
class StackOnly
{
public:
StackOnly()
{}
private:
//C++98
void* operator new(size_t size);
void operator delete(void* p);
//C++11
//void* operator new(size_t size) = delete;
//void operator delete(void* p) = delete;
};
new和delete的原理:
new和delete默认调用的是全局的operator new函数和operator delete函数,但如果一个类重载了专属的operator new函数和operator delete函数,那么new和delete就会调用这个专属的函数。所以只要把operator new函数和operator delete函数屏蔽掉,那么就无法再使用new在堆上创建对象了。
但该方法也有一个缺陷,就是无法防止外部在静态区创建对象。
static StackOnly obj; //在静态区创建对象
当然,也可以将方法一和方法二进行结合,结合之后就只是无法防止在静态区拷贝构造对象了。
要让一个类不能被拷贝,就要让该类不能调用拷贝构造函数和赋值运算符重载函数,因此直接将该类的拷贝构造函数和赋值运算符重载函数设置为私有,或者用C++11的方式将这两个函数删除即可。
代码如下:
class CopyBan
{
public:
CopyBan()
{}
private:
//C++98
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//C++11
//CopyBan(const CopyBan&) = delete;
//CopyBan& operator=(const CopyBan&) = delete;
};
方法一:C++98
将该类的构造函数设置为私有即可,因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但父类的私有成员在子类当中是不可见的,所以在创建子类对象时子类无法调用父类的构造函数对父类的成员进行初始化,因此该类被继承后子类无法创建出对象。
代码如下:
class NonInherit
{
public:
static NonInherit CreateObj()
{
return NonInherit();
}
private:
//将构造函数设置为私有
NonInherit()
{}
};
方法二:C++11
C++98的这种方式其实不够彻底,因为这个类仍然可以被继承(编译器不会报错),只不过被继承后无法实例化出对象而已。于是C++11中提供了final关键字,被final修饰的类叫做最终类,最终类无法被继承,此时就算继承后没有创建对象也会编译出错。
代码如下:
class NonInherit final
{
//...
};
什么是单例模式?
单例模式有两种实现方式,分别是饿汉模式和懒汉模式:
饿汉模式
单例模式的饿汉实现方式如下:
代码如下:
class Singleton
{
public:
//3、提供一个全局访问点获取单例对象
static Singleton* GetInstance()
{
return _inst;
}
private:
//1、将构造函数设置为私有,并防拷贝
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
//2、提供一个指向单例对象的static指针
static Singleton* _inst;
};
//在程序入口之前完成单例对象的初始化
Singleton* Singleton::_inst = new Singleton;
懒汉模式
单例模式的懒汉实现方式如下:
代码如下:
class Singleton
{
public:
//3、提供一个全局访问点获取单例对象
static Singleton* GetInstance()
{
//双检查
if (_inst == nullptr)
{
_mtx.lock();
if (_inst == nullptr)
{
_inst = new Singleton;
}
_mtx.unlock();
}
return _inst;
}
private:
//1、将构造函数设置为私有,并防拷贝
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
//2、提供一个指向单例对象的static指针
static Singleton* _inst;
static mutex _mtx; //互斥锁
};
//在程序入口之前先将static指针初始化为空
Singleton* Singleton::_inst = nullptr;
mutex Singleton::_mtx; //初始化互斥锁
饿汉模式和懒汉模式对比
单例对象的释放
单例对象创建后一般在整个程序运行期间都可能会使用,所以我们可以不考虑单例对象的释放,程序正常结束时会自动将资源归还给操作系统。
如果要考虑单例对象的释放,可以参考以下两种方式:
代码如下:
static void DelInstance()
{
_mtx.lock();
if (_inst != nullptr)
{
delete _inst;
_inst = nullptr;
}
_mtx.unlock();
}
代码如下:
//垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (_inst != nullptr)
{
delete _inst;
_inst = nullptr;
}
}
};
C语言和C++都是强类型语言,如果赋值运算符左右两侧变量的类型不同,或形参与实参的类型不匹配,或返回值类型与接收返回值的变量类型不一致,那么就需要进行类型转换。
C语言中有两种形式的类型转换,分别是隐式类型转换和显式类型转换:
(指定类型)变量
的方式进行类型转换。需要注意的是,只有相近类型之间才能发生隐式类型转换,比如int和double表示的都是数值,只不过它们表示的范围和精度不同。而指针类型表示的是地址编号,因此整型和指针类型之间不会进行隐式类型转换,如果需要转换则只能进行显式类型转换。比如:
int main()
{
//隐式类型转换
int i = 1;
double d = i;
cout << i << endl;
cout << d << endl;
//显式类型转换
int* p = &i;
int address = (int)p;
cout << p << endl;
cout << address << endl;
return 0;
}
C风格的转换格式虽然很简单,但也有很多缺点:
因此C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,分别是static_cast
、reinterpret_cast
、const_cast
和dynamic_cast
。
static_cast用于相近类型之间的转换,编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关类型之间转换。比如:
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int* p = &a;
// int address = static_cast(p); //error
return 0;
}
reinterpret_cast用于两个不相关类型之间的转换。比如:
int main()
{
int a = 10;
int* p = &a;
int address = reinterpret_cast<int>(p);
cout << address << endl;
return 0;
}
reinterpret_cast还有一个非常bug的用法,比如在下面的代码中将带参带返回值的函数指针转换成了无参无返回值的函数指针,并且还可以用转换后函数指针调用这个函数。
typedef void(*FUNC)();
int DoSomething(int i)
{
cout << "DoSomething: " << i << endl;
return 0;
}
int main()
{
FUNC f = reinterpret_cast<FUNC>(DoSomething);
f();
return 0;
}
说明一下: 用转换后的函数指针调用该函数时没有传入参数,因此这里打印出参数i的值是一个随机值
const_cast用于删除变量的const属性,转换后就可以对const变量的值进行修改。比如:
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl; //2
cout << *p << endl; //3
return 0;
}
说明一下:
dynamic_cast用于将父类的指针(或引用)转换成子类的指针(或引用)。
向上转型与向下转型
其中,向上转型就是所说的切割/切片,是语法天然支持的,不需要进行转换,而向下转型是语法不支持的,需要进行强制类型转换。
向下转型的安全问题
向下转型分为两种情况:
使用C风格的强制类型转换进行向下转型是不安全的,因为此时无论父类的指针(或引用)指向的是父类对象还是子类对象都会进行转换。而使用dynamic_cast进行向下转型则是安全的,如果父类的指针(或引用)指向的是子类对象那么dynamic_cast会转换成功,但如果父类的指针(或引用)指向的是父类对象那么dynamic_cast会转换失败并返回一个空指针。比如:
class A
{
public:
virtual void f()
{}
};
class B : public A
{};
void func(A* pa)
{
B* pb1 = (B*)pa; //不安全
B* pb2 = dynamic_cast<B*>(pa); //安全
cout << "pb1: " << pb1 << endl;
cout << "pb2: " << pb2 << endl;
}
int main()
{
A a;
B b;
func(&a);
func(&b);
return 0;
}
上述代码中,如果传入func函数的是子类对象的地址,那么在转换后pb1和pb2都会有对应的地址,但如果传入func函数的是父类对象的地址,那么转换后pb1会有对应的地址,而pb2则是一个空指针。
说明一下: dynamic_cast只能用于含有虚函数的类,因为运行时类型检查需要运行时的类型信息,而这个信息是存储在虚函数表中的,只有定义了虚函数的类才有虚函数表。
explicit
explicit用来修饰构造函数,从而禁止单参数构造函数的隐式转换。比如:
class A
{
public:
explicit A(int a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A a1(1);
//A a2 = 1; //error
return 0;
}
在语法上,代码中的A a2 = 1等价于以下两句代码:
A tmp(1); //先构造
A a2(tmp); //再拷贝构造
所以在早期的编译器中,当编译器遇到A a2 = 1这句代码时,会先构造一个临时对象,再用这个临时对象拷贝构造a2。但是现在的编译器已经做了优化,当遇到A a2 = 1这句代码时,会直接按照A a2(1)的方式进行处理,这也叫做隐式类型转换。
但对于单参数的自定义类型来说,A a2 = 1这种代码的可读性不是很好,因此可以用explicit修饰单参数的构造函数,从而禁止单参数构造函数的隐式转换。
RTTI(Run-Time Type Identification)就是运行时类型识别。
C++通过以下几种方式来支持RTTI:
typeid
:在运行时识别出一个对象的类型。dynamic_cast
:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。decltype
:在运行时推演出一个表达式或函数返回值的类型。C++中的4种类型转换分别是:____ 、____ 、____ 、____。
分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。
说说4种类型转换的应用场景。
本文到此结束,码文不易,还请多多支持哦!!!