c++ 模板类使用

static_cast

语法 :static_cast< 想要的类型> (原数据),static_cast显式完成 类型之间的转换。

static_cast 不做 运行时的类型检查(RTTI,Run-time type identification)。相当于 C 语言的强制类型转换,区别是static_cast 要做编译时的类型检查,强制类型转换不做检查。

A a;
B b;
a = (A)b; //编译器不做检查
a = static_cast<A>b;编译器会检查 


double d = 1.2;
int i= static_cast<int> (d); // = C语言的 (int)d;
cout << i << endl;

  1. 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
  2. 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。因为可能派生类定义了 基类中 没有的成员变量。

用法 总结
        1 static_cast常用来进行基本类型直接的转换,如char与int、int与float、enum与int之间;

        2 static_cast也可以转换用户自定义类型,但目标类型必须含有相应的构造函数;

        3 static_cast还可以转换对象的指针类型,但它不进行运行时类型检查,所以是不安全的;

        4 static_cast甚至可以把任何表达式都转换成void类型;

        5 satic_cast不能移除变量的const属性,请参考const_cast、expression、volatile、或者__unaligned属性操作符;

        6 static_cast进行的是简单粗暴的转换,所以其正确性完全由程序员自己保证。

dynamic_cast

dynamic_cast< new_type >(expression)

转换方式:
dynamic_cast< type* >(e)
 type必须是一个类类型且必须是一个有效的指针
dynamic_cast< type& >(e)
type必须是一个类类型且必须是一个左值
dynamic_cast< type&& >(e)
type必须是一个类类型且必须是一个右值

e的类型必须符合以下三个条件中的任何一个:
1、e的类型是目标类型type的公有派生类
2、e的类型是目标type的共有基类
3、e的类型就是目标type的类型。

如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

(1)指针类型
举例,Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:

if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用dp指向的Derived对象  
}
else{
  //使用bp指向的Base对象  
}


值得注意的是,在上述代码中,if语句中定义了dp,这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。

(2)引用类型

因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。

void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Base &>(b);  
   //使用b引用的Derived对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的情况
 }
}


四、转换注意事项:
尽量少使用转型操作,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代。

unique_lock & std::lock_guard


std::unique_lock比std::lock_guard更灵活,这种灵活性主要体现在以下几点:

lock_guard在构造时或者构造前(std::adopt_lock)就已经获取互斥锁,并且在作用域内保持获取锁的状态,直到作用域结束;而unique_lock在构造时或者构造后(std::defer_lock)获取锁,在作用域范围内可以手动获取锁和释放锁,作用域结束时如果已经获取锁则自动释放锁。
lock_guard锁的持有只能在lock_guard对象的作用域范围内,作用域范围之外锁被释放,而unique_lock对象支持移动操作,可以将unique_lock对象通过函数返回值返回,这样锁就转移到外部unique_lock对象中,延长锁的持有时间。

int n;
std::mutex some_mutex;

void prepare_data()
{
    cout << n++ << endl;
}

void do_something()
{
    cout << n++ << endl;
}

std::unique_lock<std::mutex> get_lock()
{
    std::unique_lock<std::mutex> lk(some_mutex);//与lock_guard相同,构造时获取锁
    cout << "owns_lock? " << lk.owns_lock() << endl;//1
    prepare_data();
    return lk;
}

int main()
{
    //unique_lock基本使用
    std::mutex mutex2;
    std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);//告诉构造函数暂不获取锁
    cout << "owns_lock? " << lock2.owns_lock() << endl;//0
    lock2.lock();//手动获取锁
    std::cout << "owns_lock? " << lock2.owns_lock() << endl;//1
    lock2.unlock();//手动解锁
    cout << "owns_lock? " << lock2.owns_lock() << endl;//0
    //锁所有权转移到函数外部
    std::unique_lock<std::mutex> lk(get_lock());//
    do_something();
}
//析构
//lock2未获取锁mutex2,因此不会调用unlock
//lk对象持有锁some_mutex,调用unlock

由于unique_lock对象需要根据当前对象是否已经持有锁还是未持有进行判断从而执行适当的操作,因此比lock_guard占用空间稍大一点,效率稍低一点,std::unique_lock.owns_lock返回当前是否持有锁。
通常来说不应该在持有锁的期间执行消耗时间长的操作,此时unique_lock更加灵活,可以随时unlock,避免不相关的操作期间仍然持有锁。

std::unique_lock<std::mutex> my_lock(the_mutex);
some_class data_to_process = get_next_data_chunk();
my_lock.unlock();
result_type result = process(data_to_process);
my_lock.lock();
write_result(data_to_process, result);
————————————————
版权声明:本文为CSDN博主「mrbone11」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mrbone11/article/details/121620329

std::unique_lock也会在析构的时候自动解锁,所以说,是std::lock_guard的功能超集。

是一个类模板,可以取代lock_guard(),相比起来前者更灵活,但效率较差,内存占用较多 ,一般工作中推荐使用lock_guard()

unique_lock()的第二个参数:
2.1 std::adopt_lock:表示互斥量已经被lock,则在第二个参数添加此标志。(使用此参数则当前线程之前未使用lock())。

2.2 std::try_to_lock():尝试给mute上lock(),如果没有锁定成功会立即返回,并不会产生阻塞。

解释: 线程 2 拿到锁后陷入时长为dura的阻塞期,由于线程间独立运行,这时另一个线程不断通过try_to_lock()尝试取锁,继续运行,这样可以防止因为锁的原因导致整个程序陷入等待状态,浪费大量CPU资源。

2.3 std::defer_lock:初始化mutex但不lock()。搭配unique_lock的成员函数使用。

unique_lock成员函数:
1、lock():加锁
2、unlock():解锁

	std::unique_lock<std::mutex>myLockGuard(my_mutex1,std::defer_lock);
	myLockGuard.lock();
	//处理共享代码...
	myLockGuard.unlock();
	//处理非共享代码...
	myLockGuard.lock();
	//处理共享代码...
	msgRecvQueue.push_back(i);
  • unique_lock成员函数:
    1、 try_lock():尝试加锁,不阻塞,返回值为bool。
	std::unique_lock<std::mutex>myLockGuard(my_mutex1, std::defer_lock);
	if (myLockGuard.try_lock() == true)
		cout << "拿到锁" << endl;
	else
		cout << "未拿到锁" << endl;
  • unique_lock成员函数:
    1、 release():返回所管理的mutex对象指针,并释放所有权,即该unique_lock与mutex不再有关系。
    2、 若该mutex处于加锁状态,记得自己进行解锁代码编写。
    3、 返回值是一个mutex指针,通常创建mutex指针接受并进行手动解锁。

std::unique_lock<std::mutex>myLockGuard(my_mutex1);//已加锁
	std::mutex* ptm = myLockGuard.release();//接触myLockGuard与my_mutex1的关系

	msgRecvQueue.push_back(i);

	ptm->unlock();//手动解锁
	//my_mutex1.unlock();

互斥量锁住的代码量称为锁的粒度,用粗细来描述,粒度越细,锁住代码越少,程序效率越高。

 std::condition_variable

(1)、如果不满足该条件,拥有互斥锁的线程应该释放该互斥锁,把自身阻塞(block)并挂到(suspend)条件变量的线程队列中

 (2)、如果满足该条件,拥有互斥锁的线程在临界区内访问共享资源,在退出临界区时通知(notify)在条件变量的线程队列中处于阻塞状态的线程,被通知的线程必须重新申请对该互斥锁加锁。

 std::condition_variable类的成员函数:

 (1)、构造函数:仅支持默认构造函数,拷贝、赋值和移动(move)均是被禁用的。

 (2)、wait:当前线程调用wait()后将被阻塞,直到另外某个线程调用notify_*唤醒当前线程;当线程被阻塞时,该函数会自动调用std::mutex的unlock()释放锁,使得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常是另外某个线程调用notify_*唤醒了当前线程),wait()函数也是自动调用std::mutex的lock()。wait分为无条件被阻塞和带条件的被阻塞两种。

 无条件被阻塞:调用该函数前,当前线程应该已经对unique_lock<mutex> lck完成了加锁。所有使用同一个条件变量的线程必须在wait函数中使用同一个unique_lock<mutex>。该wait函数内部会自动调用lck.unlock()对互斥锁解锁,使得其他被阻塞在互斥锁上的线程恢复执行。使用本函数被阻塞的当前线程在获得通知(notified,通过别的线程调用 notify_*系列的函数)而被唤醒后,wait()函数恢复执行并自动调用lck.lock()对互斥锁加锁。

 带条件的被阻塞:wait函数设置了谓词(Predicate),只有当pred条件为false时调用该wait函数才会阻塞当前线程,并且在收到其它线程的通知后只有当pred为true时才会被解除阻塞。因此,等效于while (!pred())  wait(lck).

 (3)、wait_for:与wait()类似,只是wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其它线程的通知,wait_for返回,剩下的步骤和wait类似。

 (4)、wait_until:与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其它线程的通知,wait_until返回,剩下的处理步骤和wait类似。

 (5)、notify_all: 唤醒所有的wait线程,如果当前没有等待线程,则该函数什么也不做。

 (6)、notify_one:唤醒某个wait线程,如果当前没有等待线程,则该函数什么也不做;如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的模板类是一种通用的类模板,可以定义具有通用行为的类,用于处理各种不同类型的数据。下面是使用C++模板类的基本方法: 1. 定义模板类使用关键字`template`和模板参数列表来定义一个模板类。模板参数列表可以包含类型参数和非类型参数。 ```cpp template <typename T> class TemplateClass { // 类的成员和方法定义 }; ``` 2. 实例化模板类: 在使用模板类之前,需要根据实际需要对其进行实例化来创建特定类型的对象。 ```cpp TemplateClass<int> obj1; // 实例化一个存储int类型数据的对象 TemplateClass<double> obj2; // 实例化一个存储double类型数据的对象 ``` 3. 使用模板类的成员和方法: 可以像使用普通类一样使用模板类的成员和方法。 ```cpp obj1.memberVariable = 10; // 设置成员变量的值 int value = obj1.memberFunction(); // 调用成员函数并获取返回值 ``` 4. 特化模板类(可选): 如果需要针对特定类型提供不同的实现,可以使用模板类的特化来为特定类型定义特定的行为。 ```cpp template <> class TemplateClass<char> { // 特定类型char的特化实现 }; ``` 注意事项: - 模板类的定义通常放在头文件中,以便在需要的地方进行包含。 - 模板类的成员函数的实现通常也需要放在头文件中,以避免链接错误。 - 模板类可以有多个模板参数,可以使用模板元编程技术进行更高级的操作。 - 在使用模板类时,编译器会根据实例化的类型生成对应的代码。每个类型的实例化都会生成不同的类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值