C++使用记录001——自定义shared_ptr

起因

在学习DirectX的时候,出于给其中各种对象添加自动释放的功能,想要开发一个对其对象封装的函数,在析构的时候会自动调用Release函数,最开始考虑的是使用unique_ptr结构,只需要重写右值构造函数即可,但是由于D3DXCreateTextureFromFileEx函数必须用到IDirect3DDevice9对象的指针,因此不得不与Direct3D组件耦合起来,在通过Direct3D中调用的LoadTexture方法返回DXTexture右值引用又会让编译器弹出警告"warning C4172: 返回局部变量或临时变量的地址",想了想这样弄怕是要出事儿,在赋值之前那个临时对象就已经被销毁了,而返回普通对象似乎又由于没有实现默认拷贝构造函数而被否决了,即使用std::move也无法返回,由于不清楚C++默认的unique_ptr是如何解决这个问题的,所以老老实实去实现shared_ptr了

过程

第一个版本

  1. 最初的版本是没有默认构造函数的,每一个pointer必须绑定一个有效的DX对象
  2. 在正式应用时我意外的发现,在之前开发时,我实现定义了一个全局变量存储加载的纹理指针,要更换为自动释放的纹理对象的话,就必须提供默认构造函数,因此加入了默认构造函数(初始化为nullptr),同时为了方便开发,在有参构造函数中若传进来的参数为nullptr的时候,引用计数也会被初始化为nullptr
  3. 此时的代码是:
#ifndef VYTERM_DIRECTX_OBJECT_SHARED_POINTER_HPP_INCLUDED
#define VYTERM_DIRECTX_OBJECT_SHARED_POINTER_HPP_INCLUDED

namespace vyt
{
	namespace dx
	{
		template <typename T>
		class SPointer
		{
			T m_object;
			int *m_useCount;
			void Copy(const SPointer& texture)
			{
				m_object = texture.m_object;
				m_useCount = texture.m_useCount;
				*m_useCount += 1;
			}
			void Release()
			{
				if (nullptr == m_object) return;
				*m_useCount -= 1;
				if (0 == *m_useCount)
				{
					delete m_useCount;
					m_object->Release();
				}
				m_useCount = nullptr;
				m_object = nullptr;
			}
		public:
			SPointer() : m_object(nullptr), m_useCount(nullptr) { }
			explicit SPointer(T texture)
				: m_object(texture), m_useCount(nullptr == texture ? nullptr : new int(1)) { }

			SPointer(const SPointer& texture)
				: m_object(texture.m_object), m_useCount(texture.m_useCount) {
				*m_useCount += 1;
			}
			SPointer& operator=(const SPointer& texture)
			{
				if (this == &texture) return *this;
				Release();
				Copy(texture);
				return *this;
			}

			SPointer(SPointer&& texture)
				: m_object(texture.m_object), m_useCount(texture.m_useCount) {
				*m_useCount += 1; texture.Release();
			}
			SPointer& operator=(SPointer&& texture)
			{
				if (this == &texture) return *this;
				Release();
				Copy(texture);
				texture.Release();
				return *this;
			}
			~SPointer() { Release(); }

			T& operator*() { return m_object; }
			const T& operator*() const { return m_object; }
		};
	}
}

#endif

遇到的问题

第一个版本在正常加载的时候没有出现什么问题,但是在我想到去将开头吐槽的“
D3DXCreateTextureFromFileEx函数必须用到IDirect3DDevice9对象的指针”解耦时,测试了一下在调用D3DXCreateTextureFromFileEx时将其需要的IDirect3DDevice9对象的指针传递为nullptr,此时加载失败,返回的是一个无效的对象,按说写的SPointer已经对空指针的情况做了相应的处理,却直接给报了个错误,在Copy方法被调用时会试图对值为nullptr的m_useCount加一,这个问题挺容易解决的,我在Copy函数中相应的操作前加了if (nullptr != m_useCount)的判断,但是……

重新调试的时候这个错误仍然存在?非常神奇,查看反汇编的时候并没有看到相应条件跳转代码,于是猜测“可能是VS将这句代码优化了”,接着就是长达一个多小时的调试,最后定义了一个static的nullptrUsecount来代替nullptr操作,重写了Release中的判断条件,总算是弄好了。
修改后的部分代码 :

		// 静态的引用计数,当object为nullptr时使用避免其他函数不工作
		static int *nullptrUsecount = new int(1);
		template <typename T>
		class SPointer
		{
			T m_object;
			int *m_useCount;
			void Copy(const SPointer& texture)
			{
				m_object = texture.m_object;
				m_useCount = texture.m_useCount;
				*m_useCount += 1;
			}
			void Release()
			{
				// 由于在右值构造函数和析构函数中都有可能调用Release函数,因此判断m_useCount
				if (nullptr == m_useCount) return;
				*m_useCount -= 1;

				if (0 == *m_useCount)
				{
					delete m_useCount;

					if (nullptr != m_object)
						m_object->Release();
				}
				m_useCount = nullptr;
				m_object = nullptr;
			}
		public:
			// 静态的引用计数同样需要增加减少计数,因此对其引用的地方增加了一次引用
			SPointer() : m_object(nullptr), m_useCount(nullptrUsecount) { *m_useCount += 1; }
			explicit SPointer(T texture)
				: m_object(texture), m_useCount(nullptr == texture ? nullptrUsecount : new int(0)) { *m_useCount += 1; }

在调试过程中不断收到“源代码与原始版本不同”的提示,断点都需要在条件中设置一下不要求源文件与原始版本完全匹配才能正常启用,这是模板的具体实现方式咱心里有数,在我完成了上述艰难的修改后,这家伙给提前调用了Release函数,我可就纳闷了,查看反汇编,这次倒是有跳转,但是跳转的位置不对,在if (0 == *m_useCount)中应该是判断相等则执行下面的代码否则清空指针结束函数,但实际执行的汇编代码则是相等则减少计数,无论相不相等都会判断m_object是否为空并释放它,这是我想到了,在我上一次修改的时候正包含了前半部分逻辑(相等则减少计数)!于是,重新生成项目,问题解决……

总结

简单说,我认为是自己小题大做了,在遇到问题的时候不去想在现有条件下找找其他可能潜在的问题,而是想着试一下新思路来重写核心代码,在最开始,明明是重新生成一下就能解决的问题,我却花了一个多小时去调试并用nullobject模式重写对引用计数的封装,还带来的性能上的浪费,实在是不该呢。
“可能是VS将这句代码优化了” 这样的推断一旦产生,首先应该是重启VS并重新生成项目,而非是想办法在这句代码不存在的时候继续想其他途径解决问题,那应该是确定无法从最简单的途径解决时才考虑的次要选项,绝不是首选解决方案

最后附上最终版本的完整代码

#ifndef VYTERM_DIRECTX_OBJECT_SHARED_POINTER_HPP_INCLUDED
#define VYTERM_DIRECTX_OBJECT_SHARED_POINTER_HPP_INCLUDED

namespace vyt
{
	namespace dx
	{
		template <typename T>
		class SPointer
		{
			T m_object;
			int *m_useCount;
			void Copy(const SPointer& sp)
			{
				m_object = sp.m_object;
				m_useCount = sp.m_useCount;
				if (nullptr != m_useCount)
					*m_useCount += 1;
			}
			void Release()
			{
				if (nullptr == m_useCount) return;
				*m_useCount -= 1;

				if (0 == *m_useCount)
				{
					delete m_useCount;

					if (nullptr != m_object)
						m_object->Release();
				}
				m_useCount = nullptr;
				m_object = nullptr;
			}
		public:
			SPointer() : m_object(nullptr), m_useCount(nullptr) { }
			explicit SPointer(T object)
				: m_object(object), m_useCount(nullptr == object ? nullptr : new int(1)) { }

			SPointer(const SPointer& sp)
				: m_object(sp.m_object), m_useCount(sp.m_useCount) {
				*m_useCount += 1;
			}
			SPointer& operator=(const SPointer& object)
			{
				if (this == &object) return *this;
				Release();
				Copy(object);
				return *this;
			}

			SPointer(SPointer&& sp)
				: m_object(sp.m_object), m_useCount(sp.m_useCount) {
				*m_useCount += 1; sp.Release();
			}
			SPointer& operator=(SPointer&& sp)
			{
				if (this == &sp) return *this;
				Release();
				Copy(sp);
				sp.Release();
				return *this;
			}
			~SPointer() { Release(); }

			T& operator*() { return m_object; }
			const T& operator*() const { return m_object; }
			bool operator==(std::nullptr_t np) const { return np == *m_object; }
			friend bool operator==(std::nullptr_t np, const SPointer &sp) { return sp == np; }
		};
	}
}

#endif

2019,加油!

发表评论