如何保证对象的安全

线程下的对象安全是容易保证的,因为构造与析构都由自己控制,所以本线程可以控制当前对象的生命周期,但是多线程下,对象的存在与否就变得扑簌迷离了。

1.保证对象线程安全的第一步,构造函数。

1 构造函数可以注册回调函数吗?
不可以,因为在对象的构造中,对象可能还没初始化完成,其他线程通过回调函数调用这个有可能是半成品的对象,后果难以预计。
1 那我把这个回调函数写在最后一行,此时对象已经构造成功了,是不是就可以了?
也不可以,当这个类是基类,基类构造成功了,但是派生类还要继续执行构造,此时通过this指针访问派生类的内存,后果难以预计

总结:主旨就是不能在构造函数中泄露this指针
附加:初始化列表和大括号的区别
对象的关系有3种:
组合、关联、聚合
当使用组合时,内嵌类的初始化必须使用初始化列表来初始化。
初始化列表是初始化,括号里时赋值。用的时候一般感觉不大,但是其实是有区别的,所以尽量使用初始化列表会好一点。

class Cdata
{
public:
	Cdata(int x, int y)
		:x_(x)
		,y_(y)
	{}
	void show()
	{
		cout << x_ << "年" << y_ << "月" << endl;
	}
private:
	int x_;
	int y_;
};
class Test
{
public:
	Test(const char* arr, int x, int y)
		:tmp(x,y)
	{
		strcpy(ptr, arr);
	}
	void show()
	{
		cout << ptr << endl;
		tmp.show();
	}
private:
	char ptr[20];
	Cdata tmp;
};

int main()
{
	Test* t1 = new Test("这是我们的未来",2020,10);
	t1->show();
}

2.第二步,析构函数。

1.如何保证对象析构的安全?使用类成员变量mutex如何?
事实上mutex并不能保证析构的安全,因为mutex是会在析构被销毁的啊

class Foo::~Foo()                                class Foo::updata()
{                                                {
	MutexLockGuard lock(mutex_);                 	MutexLockGuard lock(mutex_);
	.................                               ...............
}                                                }

在以上代码,很明显可以看出,如果析构函数在持有锁之后,成员函数阻塞,然后mutex被销毁,成员函数有可能会永远阻塞住,谁知道会发生什么。

并且在基类对象中,当调用基类析构时,派生类已经先析构了,基类拥有的mutex无法保证整个析构过程。

同时mutex造成的死锁也是很危险的,除了最常见的2锁互锁之外,拷贝构造也可能发生不易发现的死锁,这个时候找问题就是比较空难的事情了。

2.死锁是怎么回事?
当多个线程因为竞态条件而阻塞,在无外力条件下会一直阻塞下去,这就形成了死锁。

class Foo::insert()                                class Foo::updata()
{                                                {
	MutexLockGuard lock(mutex_A);                 	MutexLockGuard lock(mutex_B);
	MutexLockGuard lock(mutex_B);                   MutexLockGuard lock(mutex_A);    
	.................                               ...............
}                                                }

这就是最简单的死锁,也有可能是多个线程发生了死锁,这让我想到了线程同步问题,可以根据一个线程依赖另一个线程,来按顺序执行,但这样是危险的,串行执行最危险的就是某个线程阻塞,从而导致其余依赖的线程进入阻塞—饿死的线程。

上面的这种情况是比较明显的,下面再来看另一种情况的死锁,对于头脑简单的我还是比较难以看清的

//operator=()
//看到这里有没有想到什么
、、待续

3.用指针或者引用可以判断对象是否存活吗?
当然是不可以的,想想指针和对象的关系,指针就是指向了一块内存,不能判断对象的状态
再者 这块内存,有可能原来的对象析构了,又创建了新的对象,不管这个指针合不合法,都违背了我们的初衷。

思考一下,如果指向对象的指针置为nullptr,再调用对象的成员函数会怎么样呢?
当然会有3种情况
1.调用虚函数,万万不能。没有this指针,无法访问对象的内存,都找不到vfptr,
2.所以就引出了普通成员函数,使用成员变量,无法通过this指针获取变量,出错
3.对于不会调用this指针的成员函数是可以的,其实和普通的静态函数是没什么区别的,不依赖于this指针就可以调用成功。

3.第三步,对外调用对象的安全保证

对象x只要注册了非静态的成员函数回调,某处就会有指向x的指针,怎么解决安全问题?

归结一下就是,只要对外暴露自己的指针,那么就是危险的,所以最好不要使用指针(大神说的),但是对于现在的我感触还不是很深。
1.多线程下shared_pt如何保证对象的使用安全,一定是安全的吗?
shared_ptr可以实现对象的安全释放,他的引用计数是原子的,但是对象的读写不是安全的,和STL里的各类工具安全级别是一样的,都需要借助锁来保证安全访问。

2.内存可能出现的问题?
1.缓冲区溢出
2.空悬指针/野指针
3.重复释放
4.内存泄漏
5.不配对的new/delete
6.内存碎片
1.所以强烈使用STL,或者像muduo库自己造个buffer,但是如果应用场景类似,自己造不如直接拿来用。
2.接下来的shared_ptr与weak_ptr将会解决这个问题
3.选用合适的智能指针,让代码再也不用出现delete(事实证明想多了)
4.选用合适的智能指针
5.使用STL或者智能指针
6.待续

3.shared_ptr与weak_ptr,怎么解决循环引用的问题?
事实证明,如果不使用shared_ptr,我们就不会遇到循环引用这个问题
所以shared_ptr和weak_ptr通常是一起使用的,详解我的另一篇博客。
4.虚析构是必须的吗?
当然不是,shared_ptr可以解决这个问题,shared_ptr会捕获基类子类的指针,在析构时会自动调用其的析构函数。

class Base
{
public:
	Base()
	{ cout << "A()" << endl; }
	~Base()
	{ cout << "~A()" << endl; }
};
class Derive : public Base
{
public:
	Derive()
	{
		cout << "B()" << endl;
	}
	~Derive()
	{
		cout << "~B()" << endl;
	}
};
int main()
{
	shared_ptr<Base> p(new  Derive());
	//Base* p = new Derive();
	//delete p;
	return 0;
}

5.enable_shared_from_this的作用?(可以理解为强回调)
说实话,这个东西第一次见是在muduo里,主要与绑定器一起使用,可以将this提升为shared_ptr。但是为什么要这么做?

试想当触发回调时,我们不清楚调用的对象是否存活(终于到了最重要的地方),那我们还要不要执行这个回调?
强回调,我们保证这个对象存在,所以一定可以调用,因为传过来的是shared_ptr(其实时拷贝的,引用计数+1),但是这个对象的生命周期被拉长了,因为不是我们管理这个对象,但是却延长了这个对象的生命周期,不合理。所以弱回调。
6.弱回调
如果对象存在,我们就调用,不存在就忽略
想想强回调,我要调用的时候,我保证你活着,霸道。

所以可以用weak_ptr来管理这个对象,当要确定这个对象是否存在时,可以提升weak_ptr,如果成功,说明存在,就可以调用对象的成员方法,如果失败,那就忽略。
从而不会干扰(管理)对象的生命周期。

就像muduo里Channel在执行TcpConnection注册的回调时,要确认TcpConnection对象是否存活,用的就是弱回调。具体时Tid()方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值