2010-11-15 16:32:00 susubuhui 阅读数 536
  • 征服C++ 11视频精讲

    本教程是C++ Primer Plus 6th 、The C++ Programming Language_4th、Thinking in C++等国外顶级的C++著作的精华和个人超过10年的C++使用经验的完美结合。

    79142 人正在学习 去看看 李宁
 原文地址:
http://winx.cvs.sourceforge.net/winx/stdext/include/stdext/memory/AutoFreeAlloc.h?view=markup
http://tech.ddvip.com/2009-04/1239796996115247_9.html
/* -------------------------------------------------------------------------
// WINX: a C++ template GUI library - MOST SIMPLE BUT EFFECTIVE
// 
// This file is a part of the WINX Library.
// The use and distribution terms for this software are covered by the
// Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
// which can be found in the file CPL.txt at this distribution. By using
// this software in any fashion, you are agreeing to be bound by the terms
// of this license. You must not remove this notice, or any other, from
// this software.
// 
// Module: stdext/memory/AutoFreeAlloc.h
// Creator: xushiwei
// Email: xushiweizh@gmail.com
// Date: 2006-8-18 12:50:46
// 
// $Id: AutoFreeAlloc.h,v 1.1 2006/10/18 12:13:39 xushiwei Exp $
// -----------------------------------------------------------------------*/
#ifndef __STDEXT_MEMORY_AUTOFREEALLOC_H__
#define __STDEXT_MEMORY_AUTOFREEALLOC_H__

#ifndef __STDEXT_MEMORY_BASIC_H__
#include "Basic.h"
#endif

__NS_STD_BEGIN

// -------------------------------------------------------------------------
// class AutoFreeAlloc

template <class _Alloc, int _MemBlockSize = MEMORY_BLOCK_SIZE>
class AutoFreeAllocT
{
public:
	enum { MemBlockSize = _MemBlockSize };
	enum { HeaderSize = sizeof(void*) };
	enum { BlockSize = MemBlockSize - HeaderSize };
	enum { IsAutoFreeAlloctor = 1 };

private:
	struct _MemBlock
	{
		_MemBlock* pPrev;
		char buffer[BlockSize];
	};
	struct _DestroyNode
	{
		_DestroyNode* pPrev;
		DestructorType fnDestroy;
	};
	
	char* m_begin;
	char* m_end;
	_DestroyNode* m_destroyChain;
	_Alloc m_alloc;

private:
	_MemBlock* winx_call _ChainHeader() const
	{
		return (_MemBlock*)(m_begin - HeaderSize);
	}

	AutoFreeAllocT(const AutoFreeAllocT& rhs);
	const AutoFreeAllocT& operator=(const AutoFreeAllocT& rhs);

public:
	AutoFreeAllocT() : m_destroyChain(NULL)
	{
		m_begin = m_end = (char*)HeaderSize;
	}
	AutoFreeAllocT(_Alloc alloc) : m_alloc(alloc), m_destroyChain(NULL)
	{
		m_begin = m_end = (char*)HeaderSize;
	}

	~AutoFreeAllocT()
	{
		clear();
	}

	void winx_call swap(AutoFreeAllocT& o)
	{
		std::swap(m_begin, o.m_begin);
		std::swap(m_end, o.m_end);
		std::swap(m_destroyChain, o.m_destroyChain);
		m_alloc.swap(o.m_alloc);
	}

	void winx_call clear()
	{
		while (m_destroyChain)
		{
			m_destroyChain->fnDestroy(m_destroyChain + 1);
			m_destroyChain = m_destroyChain->pPrev;
		}
		_MemBlock* pHeader = _ChainHeader();
		while (pHeader)
		{
			_MemBlock* pTemp = pHeader->pPrev;
			m_alloc.deallocate(pHeader);
			pHeader = pTemp;
		}
		m_begin = m_end = (char*)HeaderSize;
	}

	void* winx_call allocate(size_t cb)
	{
		if ((size_t)(m_end - m_begin) < cb)
		{
			if (cb >= BlockSize)
			{
				_MemBlock* pHeader = _ChainHeader();
				_MemBlock* pNew = (_MemBlock*)m_alloc.allocate(HeaderSize + cb);
				if (pHeader)
				{
					pNew->pPrev = pHeader->pPrev;
					pHeader->pPrev = pNew;
				}
				else
				{
					m_end = m_begin = pNew->buffer;
					pNew->pPrev = NULL;
				}
				return pNew->buffer;
			}
			else
			{
				_MemBlock* pNew = (_MemBlock*)m_alloc.allocate(sizeof(_MemBlock));
				pNew->pPrev = _ChainHeader();
				m_begin = pNew->buffer;
				m_end = m_begin + BlockSize;
			}
		}
		return m_end -= cb;
	}

	void* winx_call allocate(size_t cb, DestructorType fn)
	{
		_DestroyNode* pNode = (_DestroyNode*)allocate(sizeof(_DestroyNode) + cb);
		pNode->fnDestroy = fn;
		pNode->pPrev = m_destroyChain;
		m_destroyChain = pNode;
		return pNode + 1;
	}

	void* winx_call allocate(size_t cb, int fnZero)
	{
		return allocate(cb);
	}

#if defined(_DEBUG)

	void* winx_call allocate(size_t cb, LPCSTR szFile, int nLine)
	{
		return allocate(cb);
	}

	void* winx_call allocate(size_t cb, DestructorType fn, LPCSTR szFile, int nLine)
	{
		return allocate(cb, fn);
	}

	void* winx_call allocate(size_t cb, int fnZero, LPCSTR szFile, int nLine)
	{
		return allocate(cb);
	}

#endif // defined(_DEBUG)
};

typedef AutoFreeAllocT<DefaultStaticAlloc> AutoFreeAlloc;

// -------------------------------------------------------------------------
// class TestAutoFreeAlloc

template <class LogT>
class TestAutoFreeAlloc
{
public:
	class Obj
	{
	private:
		int m_val;
	public:
		Obj(int arg = 0) {
			m_val = arg;
			printf("construct Obj: %d/n", m_val);
		}
		~Obj() {
			printf("destruct Obj: %d/n", m_val);
		}
	};

	static void doTest(LogT& log)
	{
		std::AutoFreeAlloc alloc;
		
		Obj* o1 = STD_NEW(alloc, Obj)(1);
		Obj* o2 = STD_NEW_ARRAY(alloc, Obj, 8);
		
		char* s1 = STD_ALLOC(alloc, char);
		char* s2 = STD_ALLOC_ARRAY(alloc, char, 100);
		memcpy(s2, "hello/n", 7);
		printf(s2);

		int* i1 = STD_NEW(alloc, int)(3);
		int* i2 = STD_NEW_ARRAY(alloc, int, 80);
	}
};

// -------------------------------------------------------------------------
// $Log: AutoFreeAlloc.h,v $
// Revision 1.1  2006/10/18 12:13:39  xushiwei
// stdext as independent component
//
// Revision 1.3  2006/09/26 07:51:00  xushiwei
// STL-Extension:
//  TestCase(WINX_TEST_APP, WINX_TEST_CLASS, WINX_TEST_SUITE, WINX_TEST, WINX_TEST_SUITE_END)
//  UnitTestAssert(AssertExp, AssertEq, AssertEqBuf)
//
// Revision 1.2  2006/08/19 04:40:48  xushiwei
// STL-Extension:
//   Memory(AutoFreeAlloc, RecycleBuffer, AutoArray, etc)
//   String Algorithm(trim, match, compare, etc), Container(SimpleMultiMap), CharType(isCSymbolFirstChar, etc)
//   Log(OutputLog, ErrorLog, FileLog, StringLog), PerformanceCounter, Diagnost(WINX_ASSERT, WINX_RUN_TEST, etc)
//

__NS_STD_END

#endif /* __STDEXT_MEMORY_AUTOFREEALLOC_H__ */
2012-02-02 16:31:02 fangjiaguo 阅读数 317
  • 征服C++ 11视频精讲

    本教程是C++ Primer Plus 6th 、The C++ Programming Language_4th、Thinking in C++等国外顶级的C++著作的精华和个人超过10年的C++使用经验的完美结合。

    79142 人正在学习 去看看 李宁

我们知道堆上申请的空间必须通过手动释放来回收,如果忘记这一点,很容易造成内存泄漏。智能指针的引入就是为了解决如何在正确的时机(已经没有利用价值)释放堆上申请的空间,其实现原理是通过在指针外面包一层类,并在栈上生成此类的对象,当它在栈上被自动回收的时候,将堆上的空间也释放掉。

1. 重新造一个轮子

自定义智能指针遵循以下两个原则:
1). 智能指针类必须定义为类模板,这样才能hold住所有具体类
2). 引用计数必须包含在具体类中,即只在最源头保存一个引用计数

ReferenceCounted.h

所有具体类必须继承自ReferenceCounted类。其内部包含引用计数,当被一个SmartPtr引用时就自增,当一个SmartPtr被回收时就自减。当引用计数减为0时,通过调用delete this显式地将自己回收。
#ifndef REFERENCECOUNTED_H
#define REFERENCECOUNTED_H

class ReferenceCounted
{
public:
	ReferenceCounted()
	{
		referenceCounter = 0;
	}
	void AddRef()
	{
		++referenceCounter;
	}
	void Release()
	{
		if (referenceCounter > 0 && --referenceCounter == 0)
		{
			delete this;
		}
	}
protected:
	// 虚析构函数!
	virtual ~ReferenceCounted(){}
private:
	// 引用计数
	int referenceCounter;
};

#endif

SmartPtr.h

SmartPtr类被定义为类模板,可以接收任何继承自ReferenceCounted类的具体类。
#ifndef SMARTPTR_H
#define SMARTPTR_H

// ReferenceCountedT必须是一个继承自ReferenceCounted的类
template <class ReferenceCountedT>
class SmartPtr
{
public:
	// 构造函数一:传入类型为ReferenceCountedT的指针来构造SmartPtr,必须保证p所指向的内容在堆上
	SmartPtr(ReferenceCountedT *p);
	// 构造函数二:拷贝构造函数,用一个SmartPtr来构造另一个SmartPtr
	SmartPtr(const SmartPtr<ReferenceCountedT> &p);
	~SmartPtr();
	// 重载赋值运算符一:必须保证p所指向的内容在堆上
	SmartPtr& operator =(const ReferenceCountedT *p);
	// 重载赋值运算符二:智能指针间赋值
	SmartPtr& operator =(const SmartPtr<ReferenceCountedT> &p);
	// 重载取值运算符
	ReferenceCountedT& operator *() const;
	// 重载成员选择运算符
	ReferenceCountedT* operator ->() const;
	// 重置SmartPtr的内部指针
	void Reset(ReferenceCountedT *p = NULL);
	// 获得内部指针
	ReferenceCountedT* Get();
private:
	void Dispose();
	ReferenceCountedT *ptr;
};

// 用法
// ReferenceCounted *p = new ReferenceCounted();
// SmartPtr<ReferenceCounted> sp(p);
template <class ReferenceCountedT>
SmartPtr<ReferenceCountedT>::SmartPtr(ReferenceCountedT *p)
{
	ptr = p;
	if (ptr)
	{
		ptr->AddRef();
	}
}

// 用法
// ReferenceCounted *p = new ReferenceCounted();
// SmartPtr<ReferenceCounted> sp1(p);
// SmartPtr<ReferenceCounted> sp2(sp1);
template <class ReferenceCountedT>
SmartPtr<ReferenceCountedT>::SmartPtr(const SmartPtr<ReferenceCountedT> &p)
{
	ptr = p.ptr;
	if (ptr)
	{
		ptr->AddRef();
	}
}

template <class ReferenceCountedT>
SmartPtr<ReferenceCountedT>::~SmartPtr()
{
	Dispose();
}

template <class ReferenceCountedT>
SmartPtr<ReferenceCountedT>& SmartPtr<ReferenceCountedT>::operator =(const ReferenceCountedT *p)
{
	Reset(p);
	return *this;
}

template <class ReferenceCountedT>
SmartPtr<ReferenceCountedT>& SmartPtr<ReferenceCountedT>::operator =(const SmartPtr<ReferenceCountedT> &p)
{
	Reset(p.ptr);
	return *this;
}

template <class ReferenceCountedT>
ReferenceCountedT& SmartPtr<ReferenceCountedT>::operator *() const
{
	return *ptr;
}

template <class ReferenceCountedT>
ReferenceCountedT* SmartPtr<ReferenceCountedT>::operator ->() const
{
	return ptr;
}

template <class ReferenceCountedT>
void SmartPtr<ReferenceCountedT>::Reset(ReferenceCountedT *p)
{
	// 避免自赋值self-assignment
	if (ptr != p)
	{
		Dispose();
		ptr = p;
		if (ptr)
		{
			ptr->AddRef();
		}
	}
}

template <class ReferenceCountedT>
ReferenceCountedT* SmartPtr<ReferenceCountedT>::Get()
{
	return ptr;
}

template <class ReferenceCountedT>
void SmartPtr<ReferenceCountedT>::Dispose()
{
	if (ptr)
	{
		ptr->Release();
	}
}

#endif
测试代码:

MyClass.h

MyClass类表示一个具体类,继承自ReferenceCounted。
#include "ReferenceCounted.h"

class MyClass : public ReferenceCounted
{
public:
	MyClass()
	{
		num = 0;
	}
	int MyFoo()
	{
		++num;
		return num;
	}
	int num;
protected:
	~MyClass(){}
};

Main.cpp

#include "SmartPtr.h"
#include "MyClass.h"
#include <crtdbg.h>
#include <vector>
#include <iostream>
using namespace std;

void main(void)
{
	{
		MyClass *rc1 = new MyClass();
		SmartPtr<MyClass> sp1 = rc1; // 普通构造函数
		SmartPtr<MyClass> sp2 = sp1; // 拷贝构造函数
		SmartPtr<MyClass> sp3(rc1); // 普通构造函数
		SmartPtr<MyClass> sp4(sp1); // 拷贝构造函数

		MyClass *rc2 = new MyClass();
		sp3 = rc2; // 赋值运算符
		sp4 = sp3; // 赋值运算符

		cout << (*sp1).MyFoo() << endl; // 像指针一样操作
		cout << sp1->MyFoo() << endl;

		vector<SmartPtr<MyClass>> v; // 在vector中的表现
		v.push_back(sp1);
		v.push_back(sp2);
		v.push_back(sp3);
		v.push_back(sp4);
	}
	_CrtDumpMemoryLeaks();
}

2. C++自带智能指针研究


2019-09-08 22:50:39 weixin_42073412 阅读数 398
  • 征服C++ 11视频精讲

    本教程是C++ Primer Plus 6th 、The C++ Programming Language_4th、Thinking in C++等国外顶级的C++著作的精华和个人超过10年的C++使用经验的完美结合。

    79142 人正在学习 去看看 李宁


有些地方是不适合使用C++语言的。比如C++的虚函数,垃圾回收,异常,在底层开发中使用,反而会造成很多不必要的麻烦。比如C++编译器为了重载函数,其编译出来的函数名会被改成包括参数的形式(换名),而且每个编译器都有自己的内部前缀和后缀,这一点尤其在操作系统编写中会造成麻烦,因为操作系统的系统调用使用汇编,比如中断的实现中,就需要调用汇编中断服务,然后在其中回调操作系统内核的C函数,如果使用C++函数,就不能正确指定回调函数名。

那么如何使用C语言来实现C++的类?

C语言中可以和class类比的类型就是struct了,另外还有union, 但union并不具备class的条件。

(union(共用体)——完全就是共用一个内存首地址,并且各种变量名都可以同时使用,操作也是共同生效。内存的大小与成员中长度的最大的决定;)

如何使用C语言才能让struct实现class呢?

C语言支持函数指针的定义,并且struct中也支持函数指针定义。

int func(int a, int b);
定义函数指针:

int (*pfunc)(int, int);

当定义pfunc = func时,下面两个调用是一样的:

func(10, 20);
pfunc(10, 20);

将函数指针定义到struct中:

#include<stdio.h>

typedef struct A 
{
    int data;
    int (*Val)(int a); 
}A;

int Val(int a)  
{
    return a;
}

int main()
{
    A a;
    a.Val = Val;
    printf("%d",a.Val(10)); //输出:10
}

这样可以得到10的结果。

我们知道class中隐含了一个this指针,那在Val函数中怎样才能得到this呢?

对了,就通过参数传递:

#include<stdio.h>

typedef struct A A;
struct A
{
    int data;
    int (*Val)(A* that, int a); 
};
    
int Val(A* that, int a)  
{
    return that->data + a;
}

int main()
{
    A a;
    a.data=8;
    a.Val = Val;
    printf("%d",a.Val(&a, 10));  //输出:18
}

使用that来代替this,这样如果这段代码拿到C++编译器下面时也不会跟struct中隐含的this冲突。这样就定义了struct来代替class,唯一的缺点是定义好以后,每次调用函数需要将对象指针传递进去,这也是无可避免的。


进阶1: 构造函数

上一步中,a.Val = Val;写在外面,如果有好几个成员(函数),就会很麻烦,每一个对象的赋值都要重复相似操作。这时就需要定义一个构造函数。

#include<stdio.h>

typedef struct A A;
struct A
{
    int data;
    int (*Val)(A* that,int a);
};

int Val(A* that,int a) 
{
	return that->data + a;
}

A* _A(A* that, int data)
{
	that->data = data;
	that->Val = Val;
	return that;
}

int main()
{
	A a;
	_A(&a, 20);
	printf("%d",a.Val(&a, 10));
}

这样定义一个对象并赋值就只需要两行代码。

注意:这里构造函数只能是一个普通函数,不能作为成员(函数),因为构造函数只调用一次,没有作为成员的必要,并且构造函数,如果是成员也没法在构造前知道构造函数是什么,因此只能在外部指定。

进阶2:继承

实现继承才是使用类的终极目标。

这里先暂时、而且也没法实现虚函数之类的,不用考虑这些。

实现继承需要用到上面提到的union

#include<stdio.h>

typedef struct A A;
struct A
{
    int data;
    int (*Val)(A* that,int a);
};

int Val(A* that,int a) 
{
	return that->data + a;
}

A* _A(A* that, int data)
{
	that->data = data;
	that->Val = Val;
	return that;
}

typedef struct B B;
struct B 
{
	union
	{
		A super;
		struct 
		{
			int data;
			int (*Val)(A* that, int a);
		};
	};
	int val;
};
B* _B(B* that, int val) 
{
	_A(&that->super,val);
	that->val = val;
}
int main()
{
	B b;
	_B(&b, 30);
	printf("%d\n",b.Val(&b.super, 20));
}

在union中,定义了基类A,以及将基类A中的成员都拷贝到了一个匿名struct中。在C规范中,union的匿名struct后面定义一个变量名,那么使用变量名.成员才能得到变量,而如果没有变量名,则直接使用成员名就能得到变量,如下:

union 
{
    float f[2];
    struct
    {
        float f1;
        float f2;
    }uf;
}um;

要得到f[1],使用um.fu.f2可以得到。

union 
{
    float f[2];
    struct
    {
        float f1;
        float f2;
    };
}um;

只使用um.f2就能得到f[1]。

利用这点,可以让基类的成员变成继承类的成员。继承类中super部分是基类,而自身又定义了val这个成员变量,是属于基类以外的,而且更有意思的是,在B的构造函数中,可以直接通过that->super来构造a,并且构造函数完了以后,b.data和b.Val就是构造A以后的成员,它们分别等于b.super.data和b.super.Val。

 

2007-09-01 23:16:00 winux 阅读数 6735
  • 征服C++ 11视频精讲

    本教程是C++ Primer Plus 6th 、The C++ Programming Language_4th、Thinking in C++等国外顶级的C++著作的精华和个人超过10年的C++使用经验的完美结合。

    79142 人正在学习 去看看 李宁
一、简介

这是一个自己写C++垃圾自动回收器,用到的都是标准C++语法。采用了引用计数加mark-sweep的方法。在没有循环引用的情况下,引用计数可以保证垃圾实时得到回收;对于有循环引用的情况下,计数就不能回收了,这时就要用mark-sweep的方法。其实完全使用mark- sweep的方法也是可以的,但有了引用计数,可以回收大量的非循环引用垃圾,减少最后的mark-sweep时的工作量。
考虑到大家的15分钟阅读热情,在说细节之前,先show一下这个指针怎么使用。顺便提一下,这个指针可以在Windows+MSVC和Linux+GCC下编译,使用。代码下载在http://download.csdn.net/source/267439
 

class A {

SmartPtr<B> b; 

};
class B : public A{

SmartPtr<A> a;

};
void main(){
   A* _a = new A(c);
   SmartPtr<A> pA = _a;
   SmartPtr<B> pB = new B(c);
   pA->b = pB;
   pB->a = pA;
 
   SmartPtr<A> pA1 = new A(c);
   pA1 = new A(c);
   pA1->b = pB;
   SmartPtr<C> pC = new C();
   SmartPtr<B> pB = pC;

   ArrayPtr<SmartPtr<A> > ps = new SmartPtr<A>[2];

   ps[0] = pA;

}
 
下面是一些细节,有兴趣的网友可以接着看。
二、C++已经提供了的辅助内存管理方法
其实,在C++的标准库里面,有一个auto_ptr的类,这个类在一定程度上帮助开发人员进行内存管理,避免内存泄露。比如可以这样用:
void myfun()

{
auto_ptr<MyClass> myObj(new MyClass());
//. . . some other operations
//myObj will be freed automatically

}
这个方法用在函数中的局部变量时非常有效,这个有效是相对于C语言时代,经常在开始的地方分配内存,然后在每一个函数return语句之前进行内存释放, C语言时代,只要足够细心,这种“人肉保证”还是靠的住的。C++代码中,由于异常的存在,使得程序常常在无法预知的地方退出,前面的这中方法也就彻底失效了。auto_ptr正是弥补了这个空缺,利用C++异常处理堆栈回卷的时候会清除所有的临时对象这个特点,在auto_ptr的析构函数里进行内存释放。
然而auto_ptr并不适合做全局对象或者要在一个大的上下文范围内活动的对象的管理。原因在于auto_ptr不允许一个对象同时被多个指针对象拥有,这样就无法在多个上下文中引用同一个对象。
不能被多个上下文同时引用,使得auto_ptr在Win32 COM编程时毫无用处。因为COM的基本思想就是服务共享,并且接口与对象分离,客户代码得到的几个不同的接口实际上可能是由同一个对象提供的。因此微软的VC++同时也提供了另外一个指针对象,就是CComPtr。与auto_ptr不同,CComPtr在指针赋值时不是把控制器从一个指针对象移交给另一个指针对象,而是调用AddRef对引用计数器加1,析构时用Release将引用计数减1,达到多个引用共享对象的目的。auto_ptr并不拥有引用计数,因而在指针析构时直接调用delete把引用对象销毁。引用计数在一定程度上实现了内存的自动管理,可以允许一个对象被多个上下文环境引用,并在合适的时候自动释放。
然而,当对象之间存在循环引用时,这种基于引用计数的方法就不能好好工作了。比如,两个对象A和B互相引用,而且A和B都不被其他对象引用。因为A和B不能被从根对象开始的引用路径访问到,就成为了内存垃圾。但是A和B相互引用,导致A和B的引用计数都是1,而不是0,这样A和B就不能被自动回收。
三、基于引用计数的垃圾回收
虽然引用计数不是完整的回收方法,但仍然是一种非常有用的方法。最重要的是,这是一种成本非常低廉的方法。就像汽车安全带相比安全气囊,前者以极其低廉的成本避免了90%的安全伤害。因此,在本文实现的内存回收方法里,依然把引用计数作为垃圾回收方法的一部分,加上额外的搜索方法完成完整的垃圾回收工作。
要使用引用计数进行垃圾回收,显然首先要实现引用计数。所谓的引用计数就是记录当前指向对象的引用个数。在C++里,也就是指向某个对象的指针的个数。当一个指针引用到一个对象时,就把这个对象的引用计数加1,这样有多少个指针引用,计数就是几。相反,在一个指针不再引用该对象时(比如指针超出了作用域,指针被赋了其他值,指针所在的对象被销毁),就把引用计数减1,当引用计数减到0时就说明没有指针在使用该对象,就可以把这个对象删除掉了。引用计数要每个对象都保留一个计数器,并且提供一个加计数的操作和减计数的操作,以及在计数值到达0时进行删除。这正是COM实现里面必不可少一部分,因此每个COM对象都会这样的一些相似的代码。显然,这是一个可以重复使用的共同部分,完全可以做成一个基类Countable,然后让需要的类从这个类继承就可以了。
这个方法仍有一个小问题。通常,在一个软件设计里面类之间的关系并不是任意的。不同的类之间会有继承关系,如果全部要求从这个能进行计数的Countable进行继承,会引入多重继承,C++设计里面经常会希望能避免多重继承。另外,程序往往会需要使用类库里面已有的类,这些类都不是从Countable继承的,而且开发者也没有这些类库的源代码,不可能修改这些已有类的定义从Countable继承。同样,对于整型,浮点型这些基本数据类型,不从任何类继承,更无法要求其从我们上面说的类继承。为了能把引用计数应用的更为广泛的,我们要换另外一个方法。
我们要实现的目的是把一个引用计数器和一个对象联系在一起。类的继承通过在编写代码时指定的继承关系,分配一个子类对象时内部自然包含父类的数据成员,形成一片连续的内存空间。这就像是用数组存储对象,一个挨着一个。除了数组,另一种常用的线性数据结构是链表,对象之间通过指针相连。同样的原理,我们也可以把引用计数和被计数的对象通过指针连接在一起。

这样完成后,客户代码引用对象时就要引用带有引用计数器的新对象。就像是在原来的对象外面又包裹了一层。因此,我们把这个提供引用计数的类命名为ObjectWrapper。

class   ObjectWrapper

{
 int count; //reference count
 void *pTarget;   //the real object allocated by usrer;
public:
 void addRef();
 void release();
 void* getTarget();
};
细心的读者看到上面的代码可能就要问了,这里为什么不使用模板?我要提醒一下,我们现在还是在试图实现一个能够被类似CComPtr的指针配合使用的对象,随后我们会实现一个类似CComPtr指针的SmartPtr。ObjectWrapper类对客户代码是透明的,在这里提供的类型信息不会对客户代码有用,另外,ObjectWrapper对象需要被一个对象管理器管理起来,使用了模板的话不同对象就会是不同的类型,给对象管理带来困难。随后就会看到SmartPtr的实现使用了模板。
在ObjectWrapper类里面,提供了addRef, release, getTarget这三个访问函数。谁,在什么时候来调用这三个函数呢?自然,这三个函数是要被客户代码使用的。但是,如果客户代码随时要想着在合适的位置调用addRef, release的话,对程序员的负担恐怕比要求用delete进行手工内存释放还要重,所谓的垃圾自动回收更是不可能的。只有能让这些操作自动进行,垃圾回收才能成为可能。因此我们接下来要实现一个SmartPtr类,作为指针对象使用。这个指针对象要
1. 在指针构造,赋值,析构时调用addRef, release管理引用计数
2. 提供C++裸指针能提供的所有操作,包括->操作符,*操作,与NULL的比较,或者临时蜕化为裸指针。好让程序员认为他真的在使用一个C++的“指针”,虽然你其实这已经被一个“对象”代替了。
SmartPtr对象包含一个指向ObjectWrapper对象的指针。
要实现第一点,只需要在SmartPtr的构造函数,拷贝构造函数,赋值操作符,析构函数里面加上对ObjectWrapper对象的addRef, release函数的调用即可。第二点,需要重载->运算符和一个隐含类型转换符。这个要复杂一点,本文就不详述了,后面的代码可以说明一切,而且,这些都不是新鲜玩意儿,在《More Effective C++》的第29条早有详细的描述了,大家可以去看这本书。
template<typename T> class SmartPtr
{
private:

 ObjectWrapper *pWrapper;

public:

 T* operator->() {return getTarget();}

 const T* operator->() const {return getTarget();}

 ~SmartPtr(void)
 {
     pWrapper->release();
 }

 SmartPtr<T>& operator=(T* p)

 {

     ObjectWrapper *old = pWrapper;

     pWrapper = WrapperManager::getInstance()->getWrapper<T>(p);

     old->release();
     return *this;
 }

 SmartPtr<T>& operator=(const SmartPtr<T> &ptr)

 {

     if(pWrapper == ptr.pWrapper)//assign to self

        return *this;

     pWrapper->release();

     pWrapper = ptr.pWrapper;

     pWrapper->addRef();
     return *this;
 }

 SmartPtr(const SmartPtr<T>& ptr)

 {
     SmartPtrManager::getInstance()->add(this);

     pWrapper = ptr.pWrapper;

     pWrapper->addRef();
 }
 SmartPtr(T* pObj=NULL)
 {
     SmartPtrManager::getInstance()->add(this);

     pWrapper = WrapperManager::getInstance()->getWrapper<T>(pObj);

 }

 operator const T* () const

 {

     return getTarget();

 }
 
 operator T* ()
 {

     return getTarget();

 }
private:

 inline const T* getTarget() const

 {

     return static_cast<T*> (pWrapper->getTarget());

 }
 

 inline T* getTarget()

 {

     return static_cast<T*> (pWrapper->getTarget());

 }
}
到此为止,我门已经实现了一个使用引用计数进行内存管理的垃圾回收。
四、用mark-sweep方法进行垃圾回收

我们前面已经说过了,只使用引用计数进行垃圾回收在有循环引用的情况下就会失效。因此我们需要更有效、更主动的回收算法。在进行更为主动的垃圾回收之前,需要解决的最基本的一个问题就是:什么样的对象是垃圾对象,也就是说,如何将垃圾对象与非垃圾对象区分开来。 解决这个问题需要能做到下面两点:

1.    能够界定对象的大小和起始位置。

2.    能够分辨对象的当前使用状态,找出垃圾对象。

对于第一个问题,之所以会出现这个问题,是因为在C++的内存管理里面,是缺乏类型信息的。也就是说,给定一个地址,我们并不知道这个地址是属于那个对象,什么类型,以及这个地址的对象大小。在这个问题上RTTI机制也无法为我们提供任何帮助,RTTI只能提供对象类型名字等有限的支持,而且只能对多态对象,也就是定义了有虚函数的对象有用。最理想的方法是如果我们能Hook所有的对象分配,自然能获得每一个对象的大小,起始地址这些第一手信息。一般,我们可以重载C++的operator new(注意,不是new operator,不是文字游戏,这两个是有区别的)来达到这个目的,但是往往有些类在设计时就已经重载了new了,而且我们的重载只能是全局的,不能代替类自己对new的重载。结果就是,如果某个类自己重载了new运算符,我们的全局重载就不会被使用,我们也就丧失了跟踪对象分配的能力。所幸,无论是VC++还是GCC都在各自的C运行库里面提供一些API函数,使得我们仍然可以得到对象的大小和起始位置,这样就成功解决了第一个问题。为了不离开垃圾回收这个主话题太远,这个方法的具体实施后面再专门讲。
第二个问题找出垃圾对象是最关键的问题。对象的使用状态可以分为可到达和不可到达两种。处于可到达状态,也就是可以被用户通过合法的引用路径引用到(C++允许用户进行任意的类型转换,你甚至可以把一个整数转换成一个地址。所以,在C++里,你“总是”有办法访问到任一个地方,但这不是我们要讨论的内容)的对象,是要保留的。而处于不可到达状态的对象就是要回收的垃圾了。前面设计的对对象进行引用计数,就是一种简单而有可靠的标识对象状态的方法。如果对象的引用计数为0,那么对象一定是不可到达,因为没有任何指针指向该对象。但这句话的逆命题,如果对象不可到达,那么引用计数一定为0,却并不正确。这是因为可能有循环引用的存在。这个时候,最直接的方法就是最可可靠的方法。我们只要从根引用开始,依次向下将所有的子对象访问一遍,能被访问到的对象就是可到达的,其他的就是不可到达的。
下面我们就开始着手将所有的对象访问一遍。为了使描述更加具体化,我以下面的代码作为例子进行讨论
class A { ... };
class B {
public:

   SmartPtr<A> pA;

   B() { pA = new A(); }

   ...
};
void main(){

   SmartPtr<B> pB = new B();

   ...
}

在这段代码里,我们分配了两类的两个对象,pA指向的A对象和pB指向的B对象,同时有两个指针pA和pB。pB是在main函数里面定义的,和整个应用程序具有相同的声明周期,是根指针。pA是处于B对象的内部,B对象销毁时这个指针也会被销毁,不是根指针。按照下面的规则,我们开始访问所有的对象:

1)    将所有的指针对象分成根指针和非根指针。但是我们并不知道根指针在什么地方创建,幸好,我们知道非根指针总是位于其他对象内部。我们依次检查pA,pB, 发现pA位于B对象内部,pB不位于任何对象内部,因此pB是根指针,pA不是。
这个工作不需要每次进行垃圾扫描时都重复进行,指针对象一旦生成,它的地位是不会变的,不可能从根指针变成非根指针,也不可能相反。只是在两次垃圾扫描的间隙时间,会有新的指针生成,需要检查新生成的指针,确定新生成的指针的类型。

2)    对于每一个根指针,访问它只向的对象,并对该对象内部的所有指针,重复这个过程。从pB,我们访问到了B对象,然后在B对象内部,我们发现了pA指针,然后重复,从pA指针访问到A对象,在A对象内部,没有发现任何指针。
每次访问到一个对象,我们就将这个对象标记为可到达状态,因此,A对象和B对象都被标记为可到达状态。

3)    遍历所有的对象,没有被标记为可到达状态的对象就是垃圾,进行清除工作。


到此,一个完整的垃圾回收器就完成了,是不是很简单!?
里面还有一些细节,以后在继续写

1)   如何确定一个对象的大小

2)   如何找到所有的指针对象

3)   如果找到所有的用户对象

4)   如果确定一个指针是否在一个对象内部

5)   如何调用对象的正确的析构函数进行析构

6)   如何判断系统是否空闲以便进行垃圾回收

7)   垃圾回收线程与用户线程见的同步问题

8)   如何处理对象的继承与多态

9)   性能与内存开销

 
2011-11-08 18:27:29 weiqubo 阅读数 2671
  • 征服C++ 11视频精讲

    本教程是C++ Primer Plus 6th 、The C++ Programming Language_4th、Thinking in C++等国外顶级的C++著作的精华和个人超过10年的C++使用经验的完美结合。

    79142 人正在学习 去看看 李宁

一、简介
这是一个自己写C++垃圾自动回收器,用到的都是标准C++语法。采用了引用计数加mark-sweep的方法。在没有循环引用的情况下,引用计数可以保证垃圾实时得到回收;对于有循环引用的情况下,计数就不能回收了,这时就要用mark-sweep的方法。其实完全使用mark- sweep的方法也是可以的,但有了引用计数,可以回收大量的非循环引用垃圾,减少最后的mark-sweep时的工作量。
考虑到大家的15分钟阅读热情,在说细节之前,先show一下这个指针怎么使用。顺便提一下,这个指针可以在Windows+MSVC和Linux+GCC下编译,使用。代码下载在http://download.csdn.net/source/267439
 

class A

{
SmartPtr<B> b;

};
class B : public A

{
SmartPtr<A> a;

};
void main()

{
   A* _a = new A(c);
   SmartPtr<A> pA = _a;
   SmartPtr<B> pB = new B(c);  //#add没有兼容auto_ptr用法atuto_ptr<B> p(new B) ,为一不足
   pA->b = pB;
   pB->a = pA;
 
   SmartPtr<A> pA1 = new A(c);
   pA1 = new A(c);
   pA1->b = pB;
   SmartPtr<C> pC = new C();
   SmartPtr<B> pB = pC;
   ArrayPtr<SmartPtr<A> > ps = new SmartPtr<A>[2];

   ps[0] = pA;

}
 
下面是一些细节,有兴趣的网友可以接着看。


二、C++已经提供了的辅助内存管理方法


其实,在C++的标准库里面,有一个auto_ptr的类,这个类在一定程度上帮助开发人员进行内存管理,避免内存泄露。比如可以这样用:
void myfun()
{
auto_ptr<MyClass> myObj(new MyClass());
//. . . some other operations 
//myObj will be freed automatically

}
这个方法用在函数中的局部变量时非常有效,这个有效是相对于C语言时代,经常在开始的地方分配内存,然后在每一个函数return语句之前进行内存释放, C语言时代,只要足够细心,这种“人肉保证”还是靠的住的。C++代码中,由于异常的存在,使得程序常常在无法预知的地方退出,前面的这中方法也就彻底失效了。auto_ptr正是弥补了这个空缺,利用C++异常处理堆栈回卷的时候会清除所有的临时对象这个特点,在auto_ptr的析构函数里进行内存释放。
然而auto_ptr并不适合做全局对象或者要在一个大的上下文范围内活动的对象的管理。原因在于auto_ptr不允许一个对象同时被多个指针对象拥有,这样就无法在多个上下文中引用同一个对象。


不能被多个上下文同时引用,使得auto_ptr在Win32 COM编程时毫无用处。因为COM的基本思想就是服务共享,并且接口与对象分离,客户代码得到的几个不同的接口实际上可能是由同一个对象提供的。因此微软的VC++同时也提供了另外一个指针对象,就是CComPtr。与auto_ptr不同,CComPtr在指针赋值时不是把控制器从一个指针对象移交给另一个指针对象,而是调用AddRef对引用计数器加1,析构时用Release将引用计数减1,达到多个引用共享对象的目的(#add 这是核心)auto_ptr并不拥有引用计数,因而在指针析构时直接调用delete把引用对象销毁。引用计数在一定程度上实现了内存的自动管理,可以允许一个对象被多个上下文环境引用,并在合适的时候自动释放。


然而,当对象之间存在循环引用时,这种基于引用计数的方法就不能好好工作了。比如,两个对象A和B互相引用,而且A和B都不被其他对象引用。因为A和B不能被从根对象开始的引用路径访问到,就成为了内存垃圾。但是A和B相互引用,导致A和B的引用计数都是1,而不是0,这样A和B就不能被自动回收。


三、基于引用计数的垃圾回收


虽然引用计数不是完整的回收方法,但仍然是一种非常有用的方法。最重要的是,这是一种成本非常低廉的方法。就像汽车安全带相比安全气囊,前者以极其低廉的成本避免了90%的安全伤害。因此,在本文实现的内存回收方法里,依然把引用计数作为垃圾回收方法的一部分,加上额外的搜索方法完成完整的垃圾回收工作。
要使用引用计数进行垃圾回收,显然首先要实现引用计数。所谓的引用计数就是记录当前指向对象的引用个数。在C++里,也就是指向某个对象的指针的个数。当一个指针引用到一个对象时,就把这个对象的引用计数加1,这样有多少个指针引用,计数就是几。相反,在一个指针不再引用该对象时(比如指针超出了作用域,指针被赋了其他值,指针所在的对象被销毁),就把引用计数减1,当引用计数减到0时就说明没有指针在使用该对象,就可以把这个对象删除掉了。引用计数要每个对象都保留一个计数器,并且提供一个加计数的操作和减计数的操作,以及在计数值到达0时进行删除。这正是COM实现里面必不可少一部分,因此每个COM对象都会这样的一些相似的代码。显然,这是一个可以重复使用的共同部分,完全可以做成一个基类Countable,然后让需要的类从这个类继承就可以了。
这个方法仍有一个小问题。通常,在一个软件设计里面类之间的关系并不是任意的。不同的类之间会有继承关系,如果全部要求从这个能进行计数的Countable进行继承,会引入多重继承,C++设计里面经常会希望能避免多重继承。另外,程序往往会需要使用类库里面已有的类,这些类都不是从Countable继承的,而且开发者也没有这些类库的源代码,不可能修改这些已有类的定义从Countable继承。同样,对于整型,浮点型这些基本数据类型,不从任何类继承,更无法要求其从我们上面说的类继承。为了能把引用计数应用的更为广泛的,我们要换另外一个方法。


我们要实现的目的是把一个引用计数器和一个对象联系在一起。类的继承通过在编写代码时指定的继承关系,分配一个子类对象时 内部自然包含父类的数据成员,形成一片连续的内存空间。这就像是用数组存储对象,一个挨着一个。除了数组,另一种常用的线性数据结构是链表,对象之间通过指针相连。同样的原理,我们也可以把引用计数和被计数的对象通过指针连接在一起。


这样完成后,客户代码引用对象时就要引用带有引用计数器的新对象。就像是在原来的对象外面又包裹了一层。因此,我们把这个提供引用计数的类命名为ObjectWrapper。


class   ObjectWrapper

{
    int count;          //reference count
    void *pTarget;   //the real object allocated by usrer;
public:
    void addRef();
    void release();
    void* getTarget();
};
细心的读者看到上面的代码可能就要问了,这里为什么不使用模板?我要提醒一下,我们现在还是在试图实现一个能够被类似CComPtr的指针配合使用的对象,随后我们会实现一个类似CComPtr指针的SmartPtr。ObjectWrapper类对客户代码是透明的,在这里提供的类型信息不会对客户代码有用,另外,ObjectWrapper对象需要被一个对象管理器管理起来,使用了模板的话不同对象就会是不同的类型,给对象管理带来困难。随后就会看到SmartPtr的实现使用了模板。
在ObjectWrapper类里面,提供了addRef, release, getTarget这三个访问函数。谁,在什么时候来调用这三个函数呢?自然,这三个函数是要被客户代码使用的。但是,如果客户代码随时要想着在合适的位置调用addRef, release的话,对程序员的负担恐怕比要求用delete进行手工内存释放还要重,所谓的垃圾自动回收更是不可能的。只有能让这些操作自动进行,垃圾回收才能成为可能。因此我们接下来要实现一个SmartPtr类,作为指针对象使用。这个指针对象要


1. 在指针构造,赋值,析构时调用addRef, release管理引用计数
2. 提供C++裸指针能提供的所有操作,包括->操作符,*操作,与NULL的比较,或者临时蜕化为裸指针。好让程序员认为他真的在使用一个C++的“指针”,虽然你其实这已经被一个“对象”代替了。


SmartPtr对象包含一个指向ObjectWrapper对象的指针。
要实现第一点,只需要在SmartPtr的构造函数,拷贝构造函数,赋值操作符,析构函数里面加上对ObjectWrapper对象的addRef, release函数的调用即可。第二点,需要重载->运算符和一个隐含类型转换符。这个要复杂一点,本文就不详述了,后面的代码可以说明一切,而且,这些都不是新鲜玩意儿,在《More Effective C++》的第29条早有详细的描述了,大家可以去看这本书。
template<typename T>

class SmartPtr 
{
private:
     ObjectWrapper *pWrapper;

public:
     T* operator->() {return getTarget();}

     const T* operator->() const {return getTarget();}

    ~SmartPtr(void)
    {
        pWrapper->release();
    }
    SmartPtr<T>& operator=(T* p)

    {
        ObjectWrapper *old = pWrapper;

        pWrapper = WrapperManager::getInstance()->getWrapper<T>(p);

        old->release();
        return *this;
    }
    SmartPtr<T>& operator=(const SmartPtr<T> &ptr)

    {
        if(pWrapper == ptr.pWrapper)//assign to self

           return *this;

        pWrapper->release();
        pWrapper = ptr.pWrapper;

        pWrapper->addRef();
        return *this;
    }
     SmartPtr(const SmartPtr<T>& ptr)

    {
        SmartPtrManager::getInstance()->add(this);
        pWrapper = ptr.pWrapper;

        pWrapper->addRef();
    }
    SmartPtr(T* pObj=NULL)
   {
       SmartPtrManager::getInstance()->add(this);
       pWrapper = WrapperManager::getInstance()->getWrapper<T>(pObj);

    }
    operator const T* () const

    {
        return getTarget();

    }
 
    operator T* () 
    {
        return getTarget();

    }
private:
    inline const T* getTarget() const

    {
        return static_cast<T*> (pWrapper->getTarget());

    }
 
    inline T* getTarget()

    {
       return static_cast<T*> (pWrapper->getTarget());

    }
};
到此为止,我门已经实现了一个使用引用计数进行内存管理的垃圾回收。


四、用mark-sweep方法进行垃圾回收

我们前面已经说过了,只使用引用计数进行垃圾回收在有循环引用的情况下就会失效。因此我们需要更有效、更主动的回收算法。在进行更为主动的垃圾回收之前,需要解决的最基本的一个问题就是:什么样的对象是垃圾对象,也就是说,如何将垃圾对象与非垃圾对象区分开来。 解决这个问题需要能做到下面两点:

   1.能够界定对象的大小和起始位置。

   2.能够分辨对象的当前使用状态,找出垃圾对象。

 

对于第一个问题,之所以会出现这个问题,是因为在C++的内存管理里面,是缺乏类型信息的。也就是说,给定一个地址,我们并不知道这个地址是属于那个对象,什么类型,以及这个地址的对象大小。在这个问题上RTTI机制也无法为我们提供任何帮助,RTTI只能提供对象类型名字等有限的支持,而且只能对多态对象,也就是定义了有虚函数的对象有用。最理想的方法是如果我们能Hook所有的对象分配,自然能获得每一个对象的大小,起始地址这些第一手信息。一般,我们可以重载C++的operator new(注意,不是new operator,不是文字游戏,这两个是有区别的)来达到这个目的,但是往往有些类在设计时就已经重载了new了,而且我们的重载只能是全局的,不能代替类自己对new的重载。结果就是,如果某个类自己重载了new运算符,我们的全局重载就不会被使用,我们也就丧失了跟踪对象分配的能力。所幸,无论是VC++还是GCC都在各自的C运行库里面提供一些API函数,使得我们仍然可以得到对象的大小和起始位置,这样就成功解决了第一个问题。为了不离开垃圾回收这个主话题太远,这个方法的具体实施后面再专门讲。


第二个问题找出垃圾对象是最关键的问题。对象的使用状态可以分为可到达和不可到达两种。处于可到达状态,也就是可以被用户通过合法的引用路径引用到(C++允许用户进行任意的类型转换,你甚至可以把一个整数转换成一个地址。所以,在C++里,你“总是”有办法访问到任一个地方,但这不是我们要讨论的内容)的对象,是要保留的。而处于不可到达状态的对象就是要回收的垃圾了。前面设计的对对象进行引用计数,就是一种简单而有可靠的标识对象状态的方法。如果对象的引用计数为0,那么对象一定是不可到达,因为没有任何指针指向该对象。但这句话的逆命题,如果对象不可到达,那么引用计数一定为0,却并不正确。这是因为可能有循环引用的存在。这个时候,最直接的方法就是最可可靠的方法。我们只要从根引用开始,依次向下将所有的子对象访问一遍,能被访问到的对象就是可到达的,其他的就是不可到达的。
下面我们就开始着手将所有的对象访问一遍。为了使描述更加具体化,我以下面的代码作为例子进行讨论
class A { ... };
class B

{
public:
   SmartPtr<A> pA;

   B() { pA = new A(); }

   ...
};


void main()

{
   SmartPtr<B> pB = new B();

   ...
}

在这段代码里,我们分配了两类的两个对象,pA指向的A对象和pB指向的B对象,同时有两个指针pA和pB。pB是在main函数里面定义的,和整个应用程序具有相同的声明周期,是根指针。pA是处于B对象的内部,B对象销毁时这个指针也会被销毁,不是根指针。按照下面的规则,我们开始访问所有的对象:
     1. 将所有的指针对象分成根指针和非根指针。但是我们并不知道根指针在什么地方创建,幸好,我们知道非根指针总是位于其他对象内部。我们依次检查pA,pB, 发现pA位于B对象内部,pB不位于任何对象内部,因此pB是根指针,pA不是。
这个工作不需要每次进行垃圾扫描时都重复进行,指针对象一旦生成,它的地位是不会变的,不可能从根指针变成非根指针,也不可能相反。只是在两次垃圾扫描的间隙时间,会有新的指针生成,需要检查新生成的指针,确定新生成的指针的类型。

     2.对于每一个根指针,访问它只向的对象,并对该对象内部的所有指针,重复这个过程。从pB,我们访问到了B对象,然后在B对象内部,我们发现了pA指针,然后重复,从pA指针访问到A对象,在A对象内部,没有发现任何指针。
每次访问到一个对象,我们就将这个对象标记为可到达状态,因此,A对象和B对象都被标记为可到达状态。

     3.遍历所有的对象,没有被标记为可到达状态的对象就是垃圾,进行清除工作。

 



到此,一个完整的垃圾回收器就完成了,是不是很简单!?
里面还有一些细节,以后在继续写
1)如何确定一个对象的大小

2)如何找到所有的指针对象

3)如果找到所有的用户对象

4)如果确定一个指针是否在一个对象内部

5) 如何调用对象的正确的析构函数进行析构

6) 如何判断系统是否空闲以便进行垃圾回收

7) 垃圾回收线程与用户线程见的同步问题

8) 如何处理对象的继承与多态

9) 性能与内存开销

c++垃圾自动回收类

阅读数 875

c++常见问题

阅读数 171

没有更多推荐了,返回首页