C/C++开发,无可避免的内存管理(篇三)-规划好内存

news2024/9/29 11:35:20

一、用内存空间换效率

       1.1  allocatoe类模板

        在前面简述模板=顺序容器时,就提到过,标准库中的 vector 类是通过预先分配额外内存以换取不不用每次添加元素都要重新分配内存和移动元素,而是将元素直接保存加入的预先分配的内存区域。在预先分配的内存区域添加元素,使 vector 能够高效地加入元素。在预先分配内存很大可能会造成内存浪费,因为可能会创建从不使用的空对象。另外,如果预先分配的内存必须被构造,其他类就不能使用它,以及如果必须构造预先分配的内存中的对象,就不能有基类型为没有默认构造函数的类型,否则vector 没有办法知道怎样构造这些对象。

        开发者手动接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。

        C++ 提供下面两种方法分配和释放未构造的原始内存。

  • allocator 类模板,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
  • 标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始的、未类型化的内存。

        allocator 类是一个模板,它提供类型化的内存分配以及对象构造与撤销。

/*定义名为a的allocator对象,可以分配内存或构造T类型的对象*/
allocator<T> a; 
/*分配原始的未构造内存以保存 T 类型的 n 个对象*/
a.allocate(n) 
/*释放内存,在名为 p 的 T* 指针中包含的地址处保存 T 类型的 n 个对象。
*运行调用 deallocate 之前在该内存中构造的任意对象的 destroy 是用户的责任*/
a.deallocate(p,n)
/*在 T* 指针 p 所指内存中构造一个新元素。运行 T 类型的复制构造函数用 t 初始化该对象*/
a.construct(p,t)
/*运行 T* 指针 p 所指对象的析构函数*/
a.destroy(p) 
/*从迭代器 b 和 e 指出的输入范围将元素复制到从迭代器b2 开始的未构造的原始内存中。
*该函数在目的地构造元素,而不是给它们赋值。假定由 b2 指出的目的地足以保存输入范围中元素的副本*/
uninitialized_copy(b, e, b2)
/*将由迭代器 b 和 e 指出的范围中的对象初始化为 t 的副本。
*假定该范围是未构造的原始内存。使用复制构造函数构造对象*/
uninitialized_fill(b, e, t)
/*将由迭代器 b 和 e 指出的范围中至多 n 个对象初始化为t 的副本。
*假定范围至少为 n 个元素大小。使用复制构造函数构造对象*/
uninitialized_fill_n(b, e, t, n)
......

        allocator 类将内存分配和对象构造分开。当 allocator 对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator 的用户必须分别 construct 和 destroy 放置在该内存中的对象。 注意,allocator 分配出来的连续的内存区域。

        使用allocator类模板,需要添加#include <memory>支持,在std命名空间内,下面来简单研讨下allocator 类模板的用法:

//allocator测试
	std::allocator<std::string> str_alloc;//创建一个string类型的allocator对象str_alloc
	std::string* p = str_alloc.allocate(5);		  //分配5个string对象的内存
	std::string svals[5] = {"I ","am ","a ","chinese","!"};//为了方便赋值
	for (size_t i = 0; i < 5; i++)
	{
		str_alloc.construct(p+i,svals[i]);		  //构建第i个对象
	}
	for (size_t i = 0; i < 5; i++)
	{
		std::cout << *(p+i);					 //输出第i个对象的内容
	}
	std::cout << "\n";
	std::string* pdel = p+5;
	for (size_t i = 0; i < 5; i++)
	{
		/* code */
		str_alloc.destroy(--pdel);	//销毁对象
	}
	str_alloc.deallocate(p,5);		//释放内存
//out log
I am a chinese!

        上述代码可以看到,allocator 可以预先创建多少个对象内存,这些对象内存仅仅是明确自己的对象类型,根据对象类型用于分配内存空间。然后使用者可以在这些内存对象上构建该类型的具体对象,对这些对象给与读写操作。同样地,销毁对象和释放内存也是独立的两个步骤。

        1.2 仿标准库std::vector容器的Vector类模板

        std::vector容器,就是预分配模板,采用内存空间换执行效率的典型设计。现在我们采用allocator 类模板来仿造一个类std::vector容器的自定义容器Vector:

        创建源文件Vector.h、Vector.cpp,建立Vector类模板:

#ifndef _VECTOR_H_
#define _VECTOR_H_

#include <memory>

template <typename T> 
class Vector 
{
public:
    Vector(): elements(0), first_free(0), ends(0) { }
    Vector(const Vector&);
    Vector &operator=(const Vector&);
    ~Vector();
    void push_back(const T&);
    void pop_back(T& val);
    void move_back();
    std::size_t size() const;                 //Vector 的 size(实际使用的元素的数目)等于 first_free-elements
    std::size_t capacity() const;             //Vector 的 capacity(在必须重新分配 Vector 之前,可以定义的元素的总数)等于 end-elements
    std::size_t freeSize() const;             //自由空间(在需要重新分配之前,可以增加的元素的数目)是end-first_free
    typedef T   value_type;
    typedef value_type* iterator;
        
    iterator begin() const;
    iterator end() const;
private:
    std::pair<T*, T*> alloc_n_copy(const T *, const T *);
    void reallocate();              // get more space and copy existing elements
    void free();
private:
    static std::allocator<T> alloc;        // object to get raw memory
    T* elements;                    // 指向数组的第一个元素
    T* first_free;                  // 向最后一个实际元素之后的那个元素
    T* ends;                        // 指向数组本身之后的那个元素
};

#include "Vector.cpp"
#endif //_VECTOR_H_

        上述代码,每个 Vector<T> 类型定义一个 allocator<T> 类型的 static 数据成员,以便在给定类型的 Vector 中分配和构造元素。每个 Vector 对象在指定类型的内置数组中保存其元素,并维持该数组的下列三个指针:

  • elements,指向数组的第一个元素。
  • first_free,指向最后一个实际元素之后的那个元素。
  • ends,指向数组本身之后的那个元素。
    T* elements;                    // 指向数组的第一个元素
    T* first_free;                  // 向最后一个实际元素之后的那个元素
    T* ends;                        // 指向数组本身之后的那个元素

        根据这些指针来确定 Vector 的大小和容量:

  • Vector 的 size()(实际使用的元素的数目)等于 first_free-elements。
  • Vector 的 capacity()(在必须重新分配 Vector 之前,可以定义的元素的总数)等于 end-elements。
  • 自由空间freeSize()(在需要重新分配之前,可以增加的元素的数目)是end-first_free。

template <typename T> 
std::size_t Vector<T>::size() const
{
    std::size_t size_ = (std::size_t)(first_free-elements);
    return size_;
};

template <typename T> 
std::size_t Vector<T>::capacity() const
{
    std::size_t capacity_ = (std::size_t)(ends-elements);
    return capacity_;
}

template <typename T> 
std::size_t Vector<T>::freeSize() const
{
    std::size_t freeSize_ = (std::size_t)(ends-first_free);
    return freeSize_;
}

        类似vector,Vector 提供迭代器指向begin()和end(),Vector 的begin()就是elements指向地址,end()就是first_free指向地址:

    typedef T   value_type;
    typedef value_type* iterator;
        
    iterator begin() const;
    iterator end() const;

//
template <typename T> 
typename Vector<T>::iterator Vector<T>::begin() const
{
    return elements;
}

template <typename T> 
typename Vector<T>::iterator Vector<T>::end() const
{
    return first_free;
}

        提供push_back在容器尾端添加元素对象操作,提供pop_back和move_back从尾部删除元素对象操作,pop_back还能顺带取出最末端对象。

    void push_back(const T&);
    void pop_back(T& val);
    void move_back();

template <typename T>
void Vector<T>::push_back(const T& t)
{
    // 是否有可用剩余空间?
    if (first_free == ends)
        reallocate();     // 分配新空间并复制现存元素,将指针重置为指向新分配的空间
    //alloc.construct(first_free++, t);
    alloc.construct(first_free, t);//就请求 allocator 对象构造一个新的最后元素,construct 函数使用类型 T 的复制构造函数将 t 值复制到由 first_free 指出的元素
    ++first_free;       //将 first_free 加 1 以指出又有一个元素在用
}

template <typename T>
void Vector<T>::pop_back(T& val)
{
    if(nullptr==first_free) return;
    if(first_free!=elements)
    {
        val = *(--first_free);
        alloc.destroy(first_free); //删除了对象,但不释放空间
    }else{
        val = *(elements);
        alloc.destroy(elements);    //删除了对象,但不释放空间
        first_free = nullptr;
        elements = nullptr;
    }
}

template <typename T>
void Vector<T>::move_back()
{
    if(nullptr==first_free) return;
    if(first_free!=elements)
    {
        alloc.destroy(--first_free); //删除了对象,但不释放空间
    }else{
        alloc.destroy(first_free);   //删除了对象,但不释放空间
        first_free = nullptr;
        elements = nullptr;
    }
}

        首先确定是否有可用空间,如果没有,就调用 reallocate函数,reallocate 分配新空间并复制现存元素,将指针重置为指向新分配的空间。

        一旦确定知道还有空间容纳新元素,它就请求 allocator 对象构造一个新的最后元素。construct 函数使用类型 T 的复制构造函数将 t 值复制到由 first_free 指出的元素,然后,将 first_free 加 1 以指出又有一个元素对象在用 。

        删除对象,只是将对象析构,从预分配内存中解绑出来,不会干预到已经分配的内存。

        内存空间的分配和释放,由两个私有函数来完成:

void reallocate();              // get more space and copy existing elements
void free();

template <typename T> 
void Vector<T>::reallocate()
{
    std::ptrdiff_t size = first_free - elements;  //计算当前在用的元素数目
    std::ptrdiff_t newcapacity = 2 * std::max((int)size, 1);//每次重新分配时分配两倍内存,按需要扩展
    /*请求 allocator 对象来获得所需数量的空间
    *如果 Vector 保存 int 值,allocate 函数调用为 newcapacity 数目的int 值分配空间;
    *如果 Vector 保存 string 对象,它就为给定数目的 string对象分配空间。
    */
    T* newelements = alloc.allocate(newcapacity);
    //标准 copy 算法的特殊版,拷贝构造旧数据到新存储空间
    std::uninitialized_copy(elements, first_free, newelements);
    //一旦复制和撤销了元素,就释放原来数组所用的空间
    free();
    elements = newelements;//重置指针以指向新分配并初始化的数
    //将 first_free 和 ends指针分别置为指向最后构造的元素之后的单元以及所分配空间末尾的下一单元.
    first_free = elements + size;
    ends = elements + newcapacity;
}

template <typename T> 
void Vector<T>::free()
{
    for (T *p = first_free; p != elements; /* empty */ )
    {
        alloc.destroy(--p); //删除了对象,但不释放空间
    }
    if (elements)//检查 elements 是否实际指向一个数组。
    {
        // return the memory that held the elements
        alloc.deallocate(elements, ends - elements);//指向由allocate分配的空间的指针
    }
}

        每次重新分配时分配两倍内存(这可根据实际项目场景来设定一个策略)。函数首先计算当前在用的元素数目,将该数目翻倍,并请求 allocator 对象来获得所需数量的空间。如果 Vector 为空,就分配两个元素。

        在分配内存空间时,需要明确知道模板参数的对象类型:如果 Vector 保存 int 值,allocate 函数调用为 newcapacity 数目的int 值分配空间;如果 Vector 保存 string 对象,它就为给定数目的 string对象分配空间。

        std::uninitialized_copy 调用使用标准 copy 算法的特殊版本,完成拷贝构造旧数据到新存储空间。

         free函数是真正释放内存空间的,for 循环对旧数组中每个对象调用 allocator 的 destroy 成员它按逆序撤销元素,从数组中最后一个元素开始,以第一个元素结束。destroy 函数运行T 类型的析构函数来释放旧元素所用的任何资源。在调用 deallocate之前,必须检查 elements 是否实际指向一个数组。
        而reallocate调用 free函数类清除旧空间,一旦复制和撤销了元素,就释放原来数组所用的空间。最后,必须重置指针以指向新分配并初始化的数组。将 first_free 和 end指针分别置为指向最后构造的元素之后的单元以及所分配空间末尾的下一单 。

        Vector类模板提供默认、拷贝构造函数和赋值操作符,满足类基本创建对象的行为需求。

Vector(): elements(0), first_free(0), ends(0) { }
Vector(const Vector&);
Vector &operator=(const Vector&);
~Vector();

//拷贝构造函数
template <typename T>
Vector<T>::Vector(const Vector<T> &strtmp)
{
    //将形参数据拷贝给自己,std::pair<T*, T*> rsp
    std::pair<T*, T*> rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, ends,first_free
    elements = rsp.first;
    first_free = rsp.second;
    ends = rsp.second;
}

//拷贝赋值运算符
template <typename T>
Vector<T>& Vector<T>::operator=(const Vector<T> &strtmp)
{
    //防止自赋值
    if (this == &strtmp)
    {
        return *this;
    }
    //将形参数据拷贝给自己,std::pair<T*, T*> rsp
    std::pair<T*, T*> rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, cap,first_free
    elements = rsp.first;
    first_free = rsp.second;
    ends = rsp.second;
}

//析构
template <typename T>
Vector<T>::~Vector()
{
    free();
}

        下来就可以采用Vector类模板来实现类似std::vector容器类似的功能操作,为此调用案例中,还构建了std::vector对象来对标测试:

//
	Vector<int> i_vec;
	std::vector<int> i_vec_std;

	i_vec.push_back(10);
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";

	i_vec_std.push_back(10);
	std::cout << "i_vec_std.size() = " << i_vec_std.size() << "\n";
	std::cout << "i_vec_std.capacity() = " << i_vec_std.capacity() << "\n";

	for(int i=0; i<5; i++)
	{
		i_vec.push_back(i);
		i_vec_std.push_back(i);
	}
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";

	std::cout << "i_vec_std.size() = " << i_vec_std.size() << "\n";
	std::cout << "i_vec_std.capacity() = " << i_vec_std.capacity() << "\n";
	//move_back测试
	i_vec.move_back();
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";
	i_vec.move_back();
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";
	//拷贝构造测试
	Vector<int> i_vec_cpy(i_vec);
	std::cout << "i_vec_cpy.size() = " << i_vec_cpy.size() << "\n";
	std::cout << "i_vec_cpy.capacity() = " << i_vec_cpy.capacity() << "\n";
	//赋值测试
	Vector<int> i_vec_opt = i_vec;
	std::cout << "i_vec_opt.size() = " << i_vec_opt.size() << "\n";
	std::cout << "i_vec_opt.capacity() = " << i_vec_opt.capacity() << "\n";
	//仿迭代器遍历
	Vector<int>::iterator iter = i_vec.begin();
	while (iter!=i_vec.end())
	{
		std::cout << "i_vec::iter = " << *iter << "\n";
		iter++;
	}
	//pop_back测试
	int ival = 0;
	int size = (int)i_vec.size();
	for(int i=0; i<size; i++)
	{
		i_vec.pop_back(ival);
		std::cout << "ival = " << ival << "\n";
	}
//out log
i_vec.size() = 1
i_vec.capacity() = 2
i_vec_std.size() = 1
i_vec_std.capacity() = 1
i_vec.size() = 6
i_vec.capacity() = 8
i_vec_std.size() = 6
i_vec_std.capacity() = 8
i_vec.size() = 5
i_vec.capacity() = 8
i_vec.size() = 4
i_vec.capacity() = 8
i_vec_cpy.size() = 4
i_vec_cpy.capacity() = 4
i_vec_opt.size() = 4
i_vec_opt.capacity() = 4
i_vec::iter = 10
i_vec::iter = 0
i_vec::iter = 1
i_vec::iter = 2
ival = 2
ival = 1
ival = 0
ival = 10

二、new和delete表达式的秘密

        2.1 new、delete做了啥

        开发过程中,我们大多时候是通过new表达式来分配内存及创建对象和和delete 表达式来删除对象及释放内存。

int *pi = new int(100);

delete pi; pi=nullptr;

        实际上,调用new表达式,发生三个步骤。首先,该表达式调用名为 operator new 的标准
库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。

        当使用 delete 表达式,删除动态分配对象的时候,发生两个步骤。首先,对指向的对象运行适当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。

        operator new 和 operator delete 函数有两个重载版本,每个版本支持相关的new 表达式和 delete 表达式:

void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an arra

        可以使用operator new 和 operator new[]函数获得未构造内存,其有点类似 allocator 类模板的allocate函数分配未构造内存一样;operator delete 函数可用于释放内存空间,类似于allocator 类模板的deallocate 函数一样。

T* pt= static_cast<T*>(operator new(sizeof(T));
T* pts static_cast<T*>(operator new[](size*sizeof(T));

operator delete(pt)
operator delete[](pts)

        标准库函数 operator new 是分配内存但不初始化内存的。初始化内存另有其人,就是定位new表达式。定位new可以在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位 new 表达式使我们能够在特定的、预分配的内存地址构造一个对象。类似类似于allocator 类模板的construct 成员函数所做的事。

new (place_address) type
new (place_address) type (initializer-list) //initializer-list-初始化列表

new (place_address) T(t);
//等价于(template <typename T> std::allocator<T> alloc)
alloc.construct (place_address, t)

         但定位 new 表达式比 allocator 类的 construct 成员更灵活。定位 new 表达式初始化一个对象的时候,它可以使用任何构造函数,并直接建立对象。allocator 类模板的construct 函数总是使用复制构造函数。

        当然,对值型类而言,在适当的位置直接构造对象与构造临时对象并进行复制之间没有可观察到的区别,而且性能差别基本没有意义。

        但对某些类而言,使用复制构造函数是不可能的(因为复制构造函数是私有的),或者是应该避免的,在这种情况下,也许有必要使用定位 new 表达式。

        既然有定位 new 表达式对标 allocator 类的 construct 成员,那就有对标allocator 类模板的destroy 函数选择,这个就是析构函数的显式调用。显式调用析构函数的效果是适当地清除对象本身。但是,并没有释放对象所占的内存,如果需要,可以重用该内存空间,释放空间需要operator delete 函数。PS:调用 operator delete 函数不会运行析构函数,它只释放指定的内存。

T *p;
//template <typename T> std::allocator<T> alloc
alloc.destroy(p)
// call the destruc
p->~T();

        2.2 定义自己的 operator new/delete 的实现

        由此可以看到,若自行定义一个类模板TClass,可以采用这种预定义内存的方式:通过预先分配一块原始内存以保存 T对象,也许有可能改善TClass的性能。创建新T对象的时候,可以在这个预先分配的空间中构造对象。释放T对象的时候,将它们放回预先分配对象的块中,而不是将内存真正返回给系统。

        在自己的类模板中,通过定义自己的名为 operator new 和 operator delete 的成员,可以屏蔽标准库operator new 和 operator delete 函数的调用,类就可以管理用于自身类型的内存。编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

        创建一个BaseObj类模板,包含 operator new 和 operator delete 的成员,并采用std::allocator来管理对象内存。operator new 和operator delete最好成对出现,如果类定义了这两个成员中的一个,它也应该定义另一个。

#ifndef _BASE_OBJ_H_
#define _BASE_OBJ_H_

#include <memory>

template <typename T> 
class BaseObj 
{
public:
    void *operator new(std::size_t);
    void operator delete(void *, std::size_t);
    virtual ~BaseObj() { }
protected:
    T *next;
private:
    static void add_to_freelist(T*);
    static std::allocator<T> alloc_mem;
    static T *freeStore;
    static const std::size_t chunk;
};

#include "BaseObj.cpp"
#endif //_BASE_OBJ_H_

         类成员 operator new 函数具有返回类型 void* ,并接受 size_t 类型的形参。由 new 表达式用以字节计算的分配内存量初始化函数的 size_t 形参。

        类成员 operator delete 函数具有返回类型 void。它可以定义为接受单个 void* 类型形参,也可以定义为接受两个形参,即 void* 和 size_t 类型。由 delete 表达式用被 delete 的指针初始化 void* 形参,该指针可以是空指针。如果提供了 size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化 size_t 形参。

    void *operator new(std::size_t);
    void operator delete(void *, std::size_t);
    // void *operator new[](std::size_t);
    // void operator delete[](void *, std::size_t);

        BaseObj类有简单的接口:它的工作只是分配和管理已分配但未构造对象的自由列表。这个类将定义一个成员 operator new,返回自由列表的下一个元素,并将该元素从自由列表中删除。当自由列表为空的时候,operator new 将分配新的原始内存。这个类还定义 operator delete,在撤销对象时将元素放回自由列表。 

template <typename T>
void *BaseObj<T>::operator new(size_t sz)
{
    std::cout << "BaseObj operator new func is call!\n";
    // new should only be asked to build a T, not an object
    // derived from T; check that right size is requested
    if (sz != sizeof(T))
    {
        #ifdef WIN32
        throw std::runtime_error("BaseObj: wrong size object in operator new");
        #else
        std::cout << "BaseObj: wrong size object in operator new\n";
        #endif
    }
    if (!freeStore) 
    {
        // the list is empty: grab a new chunk of memory
        // allocate allocates chunk number of objects of type T
        T * array = alloc_mem.allocate(chunk);
        // now set the next pointers in each object in the allocated memory
        for (size_t i = 0; i != chunk; ++i)
        {
            add_to_freelist(&array[i]);
        }
    }
    T *p = freeStore;
    freeStore = freeStore->BaseObj<T>::next;
    return p; // constructor of T will construct the T part of the object
}

template <typename T>
void BaseObj<T>::operator delete(void *p, size_t)
{
    std::cout << "BaseObj operator delete func is call!\n";
    if (p != 0){
        // put the "deleted" object back at head of freelist
        add_to_freelist(static_cast<T*>(p));
    }
}

       BaseObj类的 new 和 delete 成员分别从自由列表取走对象和将对象返回到自由列表。然后我们希望为自己的类型使用自由列表分配策略的类将继承 BaseObj类,通过继承,这些类可以使用 BaseObj类的 operator new 和 operator delete 定义,以及表示自由列表所需的数据成员。因为打算将 BaseObj类作为基类,所以将给它一个 public 虚析构函数。

virtual ~BaseObj() { }

        由 BaseObj类定义并被它的派生类继承的数据成员是:

  • freeStore 指针,指向自由列表的表头的 static 指针。
  • next、从一个 BaseObj对象指向另一个 BaseObj对象的指针。
  • chunk 的成员指定每当自由列表为空时将分配的对象的数目。
  • add_to_freelist 函数将对象放在自由列表。

        next 指针将元素链入自由列表。从 BaseObj类派生的每个类型都包含自己的类型特定的数据,加上一个从 BaseObj基类继承的指针。每个对象具有由内存分配器使用但被继承类型自己不用的一个额外指针,对象在使用的时候,该指针无意义且不使用;对象可供使用并在自由列表中的时候,就使用 next 指针来指向下一个可用的对象。

protected:
    T *next;
private:
    static std::allocator<T> alloc_mem;
    static T *freeStore;
    static const std::size_t chunk;

        由于类定义了自己的operator new 和 operator delete 的成员,如果又想使用标准库的operator new 和 operator delete 函数,那就采用全局引用“::”来强制使用,论是在类内部还是外部调用。

	//
	ATest *ptest = new ATest();        //调用自身的成员
	//
	ATest *ptest_g = ::new ATest();    //调用标准库的函数

template <typename T>
void *BaseObj<T>::operator new(size_t sz)
{
    T*p = ::new T; // uses global operator new
    //other code
}

        2.3 让您的类按需使用自定义或是标准库的operator new/delete

        使用 BaseObj类,是构建一个通过继承BaseObj类的具体业务类DeriveClass来实现的:当继承 BaseObj类的时候,用来实例化 BaseObj类的模板类型将是派生类型本身DeriveClass。DeriveClass为了重用BaseObj类的自由列表管理而继承 BaseObj 类,而BaseObj类保存了指向它管理的对象类型的一个指针,该指针的类型是指向 BaseObj 的派生类型DeriveClass的指针。

class DeriveClass: public BaseObj<DeriveClass>
{
	public:
	DeriveClass() : pc(NULL)
	{
		pc = new char[10];
	};
	~DeriveClass(){
		if(NULL!=pc)
		{
			delete pc;
			pc = NULL;
		}
	};
	char *pc;
};

        派生类调用测试:

// 自定义类的operator new、operator delete成员
	DeriveClass *ptest = new DeriveClass();
	strcpy(ptest->pc,"hello");
	std::cout << "ptest->pc = " << std::string(ptest->pc) << "\n";
	delete ptest;
	ptest = NULL;
	//标准库的operator new、operator delete 函数
	DeriveClass *ptest_g = ::new DeriveClass();
	strcpy(ptest_g->pc,"hello");
	std::cout << "ptest_g->pc = " << std::string(ptest_g->pc) << "\n";
	::delete ptest_g;
	ptest_g = NULL;

//out log
BaseObj operator new func is call!
ptest->pc = hello
BaseObj operator delete func is call!
ptest_g->pc = hello

三、位域

        3.1 内存可以很抠搜

        如果是有做嵌入式开发,内存资源更优先,那么位域是一个节省内存资源的不错选择。位域可以声明一种特殊的类数据成员,并为数据成员指定数据位大小(一个字节8位),来保存特定的位数。位域在内存中的布局是机器相关的。位域定义如下:

//
typedef unsigned int Bit;
class MyProtocol {
    Bit mode: 2;        //模式00 01 10 11四种
    Bit modified: 1;    //读写0 1两种
    Bit pgroup: 3;      //组
    Bit pcode: 1;       //编码方式
    Bit pcheck: 1;      //是否校验
    Bit pworld1: 6;      //内容1
    Bit pworld2: 6;      //内容2
    Bit pworld3: 6;      //内容3
    Bit pworld4: 6;      //内容4
    // ...
};
//
	std::cout << "sizeof(MyProtocol) = " << sizeof(MyProtocol)  << "\n";
//out log
sizeof(MyProtocol) = 4

        3.1 位域也很有设计性

        位域必须是整型数据类型,可以是 signed 或 unsigned。通过在成员名后面接一个冒号以及指定位数的常量表达式,指出成员是一个位域":"。通常最好将位域设为 unsigned 类型。上述代码中,将一个4字节32bit的内存,分给了9个数据成员,它们占据的数据位数被强制指定,它们占据的具体数据位置,与机器相关的,例如大小端不同。

       位域的使用与类的其他数据成员相同的方式访问位域。例如,作为类的 private 成员的位域可以直接访问,而作为类的 private 成员的位域只能从成员函数的定义和类的友元访问:

//
typedef unsigned int Bit;
class MyProtocol {
public:
    Bit mode: 2;        //模式00 01 10 11四种
    Bit modified: 1;    //读写0 1两种
    Bit pgroup: 3;      //组
    Bit pcode: 1;       //编码方式
    Bit pcheck: 1;      //是否校验
private:
    Bit pworld1: 6;      //内容1
    Bit pworld2: 6;      //内容2
    Bit pworld3: 6;      //内容3
    Bit pworld4: 6;      //内容4
    // ...
public:
    void set_world1(Bit val)
    {
        pworld1 = val;
    };
    Bit get_world1()
    {
        return pworld1;
    };
};

//
	std::cout << "sizeof(MyProtocol) = " << sizeof(MyProtocol)  << "\n";
	MyProtocol mypr_;
	mypr_.mode = 1;
	std::cout << "mypr_.mode = " << mypr_.mode  << "\n";
	mypr_.mode = (Bit)10;//1010
	std::cout << "mypr_.mode = " << mypr_.mode  << "\n";	//取两位即10
	mypr_.set_world1(1);
	std::cout << "mypr_.get_world1 = " << mypr_.get_world1()  << "\n";
	mypr_.set_world1(10);//00001010
	std::cout << "mypr_.get_world1 = " << mypr_.get_world1()  << "\n";	//取6位即001010
//out log
mypr_.mode = 1
mypr_.mode = 2
mypr_.get_world1 = 1
mypr_.get_world1 = 10

        有时为了业务理解的需要,会给这些位域成员提供枚举值,便于使用者更好了解实现逻辑,增强代码可读性。

class MyProtocol {
public:
    enum { UNDEF=0, MODE1 = 0X01, MODE2 = 0X02, MODE3=0X03 }; //
    Bit mode: 2;        //模式00 01 10 11四种
    //...
};

MyProtocol mypr_;
mypr_.mode = MyProtocol::MODE2 ;
std::cout << "mypr_.mode = " << mypr_.mode  << "\n";

四、联合体

        4.1 共享内存-union特性

        联合union是一种特殊的类。一个union对象可以有多个数据成员,但在任何时刻,只有一个成员可以有值。当将一个值赋给union对象的一个成员的时候,其他所有都变为未定义的。为union对象分配的存储的量至少与包含其最大数据成员的一样多。像任何类一样,一个union定义了一个新的类型。

        联合提供了便利的办法表示一组相互排斥的值,这些值可以是不同类型的。下面这个例子,我们可能有一个处理不同各类数值或字符数据的过程。

//
union union_value {
    char c_val;
    int i_val;
    double d_val;
    unsigned long ul_val;
};

        默认情况下,union 表现得像 struct:除非另外指定,否则 union 的成员都为 public 成。当然也可以像struct一样,采用typedef来简化联合体的书写:

typedef union union_value {
    char c_val;
    int i_val;
    double d_val;
    unsigned long ul_val;
}u_val;

        然后看看联合体u_val的内存大小,如下列测试代码,u_val的内存大小和它最大内存长度的成员类型"double"是一致的:

//
	u_val uv_;
	double dv_;
	std::cout << "sizeof(u_val) = " << sizeof(u_val)  << "\n";
	std::cout << "sizeof(uv_) = " << sizeof(uv_)  << "\n";
	std::cout << "sizeof(dv_) = " << sizeof(dv_)  << "\n";
//out log
sizeof(u_val) = 8
sizeof(uv_) = 8
sizeof(dv_) = 8

        4.2 union并不简单

        union 也可以定义成员函数,包括构造函数和析构函数。但是,union 不能作为基类使用,所以成员函数不能为虚数。另外,由于是联合体共享内存,只能在构造函数初始值设定项列表中指定联合的一个成员。

//
typedef union union_value {
    //union_value() : c_val('1'),i_val(10) {}; //error
    union_value() : i_val(10) {};
    ~union_value() {};
    char c_val;
    int i_val;
    double d_val;
    unsigned long ul_val;
}u_val;
//
	u_val uv_;
	std::cout << "uv_.c_val = " << uv_.c_val << "\n";
	std::cout << "uv_.i_val = " << uv_.i_val << "\n";
	std::cout << "uv_.d_val = " << uv_.d_val << "\n";
	std::cout << "uv_.ul_val = " << uv_.ul_val << "\n";
//out log
uv_.c_val =

uv_.i_val = 10
uv_.d_val = 4.94066e-323
uv_.ul_val = 10

        union 不能具有静态数据成员或引用成员,而且,union 不能具有定义了构造函数、析构函数或赋值操作符的类类型的成员。可能有些系统及编译器可以通过编译及运行,但容易引起不可预测的后果。

class Utest
{
private:
    int val;
public:
    Utest(/* args */);
    ~Utest();
    Utest& operator=(const Utest&);
    int getVal();
};

Utest::Utest() : val(10){};
Utest::~Utest(){};
Utest& Utest::operator=(const Utest&rhs)
{
    if(this==&rhs)
        return *this;
    val = rhs.val;
    return  *this;
};
int Utest::getVal(){ return val; };

typedef union illegal_union {
    illegal_union(): puc(new Utest()) {};
    ~illegal_union(){
        if(nullptr!=puc)
        {
            delete puc;
            puc = nullptr;
        }
    };
    Utest uc;           // error: has constructor
    static int is;      // error: static member
    //int &rfi;           // error: reference member
    Utest *puc;         // ok: ordinary built-in pointer type
}i_union;

//
	std::cout << "sizeof(i_union) = " << sizeof(i_union)  << "\n";
	i_union iu_;//error 无法得到i_union默认构造函数
	std::cout << "iu_.puc->getVal() = " << iu_.puc->getVal()  << "\n";
	std::cout << "iu_.uc.getVal() = " << iu_.uc.getVal()  << "\n";	//这可能是灾难
//win out log
sizeof(i_union) = 4
iu_.puc->getVal() = 10
iu_.uc.getVal() = 1224496
//linux
test.h:63:11: 错误:有构造函数的成员‘Utest illegal_union::uc’不能用在联合中
     Utest uc;           // error: has constructor
           ^
test.h:63:11: 错误:有析构函数的成员‘Utest illegal_union::uc’不能用在联合中
test.h:63:11: 错误:有拷贝赋值运算符的成员‘Utest illegal_union::uc’不能用在联合中
test.h:63:11: 附注:unrestricted unions only available with -std=c++11 or -std=gnu++11
test.h:64:16: 错误:‘illegal_union::is’不能是静态的,因为它是联合的成员
     static int is;      // error: static member

        所以上述代码只有指针成员是可行的:

typedef union illegal_union {
    illegal_union(): puc(new Utest()) {};
    ~illegal_union(){
        if(NULL!=puc)
        {
            delete puc;
            puc = NULL;
        }
    };
    // Utest uc;           // error: has constructor
    // static int is;      // error: static member
    //int &rfi;           // error: reference member
    Utest *puc;          // ok: ordinary built-in pointer type
    int uval;            //OK
}i_union;

        另外,可以对联合体直接赋值,就像使用成员变量一样方便,只是右值要括在一对花括号中。

//
typedef union union_value {
    // union_value() : i_val(10) {};
    // ~union_value() {};
    char c_val;
    int i_val;
    double d_val;
    unsigned long ul_val;
}u_val;

//记得要注释了显示定义构造函数、赋值函数哦
	u_val uv_c = {'a'};
	std::cout << "uv_c.c_val = " << uv_c.c_val << "\n";
	uv_c = {100};
	std::cout << "uv_c.i_val = " << uv_c.i_val << "\n";
//out log
uv_c.c_val = a
uv_c.i_val = 100

        联合体成员的访问和struct类似,可以使用普通成员访问操作符(. 和 ->)访问 union 类型对象的成员。

typedef union illegal_union {
    illegal_union(): puc(new Utest()) {};
    ~illegal_union(){
        if(NULL!=puc)
        {
            delete puc;
            puc = NULL;
        }
    };
    // Utest uc;           // error: has constructor
    // static int is;      // error: static member
    //int &rfi;           // error: reference member
    Utest *puc;         // ok: ordinary built-in pointer type
    int uval;           //
}i_union;
//
    i_union iu_;
	std::cout << "iu_.puc->getVal() = " << iu_.puc->getVal()  << "\n";
	std::cout << "iu_.uval = " << iu_.uval  << "\n";

        使用 union 对象时,要确切知道 union 对象中当前存储的是什么类型的值。通过错误的数据成员检索保存在 union 对象中的值,可能会导致程序崩溃或者其他不正确的程序行为。最好能定义一个单独的对象跟踪 union 中存储了什么值。

        union 最经常用作嵌套类型,并附加一个外围成员作为union当前存储类型跟踪标记:

class UClass {
public:
    enum ValType {INT, CHAR, DBL};
    ValType tid;
    union { // unnamed union
        char cval;
        int ival;
        double dval;
    } val; // union member val
    UClass & operator=(const char &val_)
    {
        tid = CHAR;
        val.cval = val_;
        return *this;
    };
    UClass & operator=(const int &val_)
    {
        tid = INT;
        val.ival = val_;
        return *this;
    };
    UClass & operator=(const double &val_)
    {
        tid = DBL;
        val.dval = val_;
        return *this;
    };
};

//
	UClass uc_test;
	uc_test = 100;
	switch (uc_test.tid) {
		case UClass::INT:
			std::cout << uc_test.val.ival <<"\n"; break;
		case UClass::CHAR:
			std::cout << uc_test.val.cval <<"\n"; break;
		case UClass::DBL:
			std::cout << uc_test.val.dval  <<"\n"; break;
		default: break;
	}

        不用于定义对象的未命名 union 称为匿名联合。匿名 union 的成员的名字出现在外围作用域中。但要求匿名 union 不能有私有成员或受保护成员,也不能定义成员函数。

class UClass {
public:
    enum ValType {INT, CHAR, DBL};
    ValType tid;
    union { // unnamed union
        char cval;
        int ival;
        double dval;
    };//不定义联合名称
    UClass & operator=(const char &val_)
    {
        tid = CHAR;
        cval = val_;
        return *this;
    };
    UClass & operator=(const int &val_)
    {
        tid = INT;
        ival = val_;
        return *this;
    };
    UClass & operator=(const double &val_)
    {
        tid = DBL;
        dval = val_;
        return *this;
    };
};

//
	UClass uc_test;
	uc_test = 100;
	switch (uc_test.tid) {
		case UClass::INT:
			std::cout << uc_test.ival <<"\n"; break;//直接使用联合成员名称
		case UClass::CHAR:
			std::cout << uc_test.cval <<"\n"; break;
		case UClass::DBL:
			std::cout << uc_test.dval  <<"\n"; break;
		default: break;
	}

      五、源码及测试

        5.1 编译测试

        本博文源码由Vector.h/cpp、BaseObj.h/cpp、test.h、main.cpp六个组成,通过g++ main.cpp -o test.exe -std=c++11指令编译,运行程序如下:

         5.2 全部源代码文件

        main.cpp

#include "Vector.h"
#include "BaseObj.h"
#include "test.h"

#include <memory>
#include <iostream>
#include <vector>
#include <cstring>

int main(int argc, char* argv[])
{
	//allocator测试
	std::allocator<std::string> str_alloc;//创建一个string类型的allocator对象str_alloc
	std::string* p = str_alloc.allocate(5);		  //分配5个对象的内存
	std::string svals[5] = {"I ","am ","a ","chinese","!"};//为了方便赋值
	for (size_t i = 0; i < 5; i++)
	{
		str_alloc.construct(p+i,svals[i]);		  //构建第i个对象
	}
	for (size_t i = 0; i < 5; i++)
	{
		std::cout << *(p+i);					 //输出第i个对象的内容
	}
	std::cout << "\n";
	std::string* pdel = p+5;
	for (size_t i = 0; i < 5; i++)
	{
		/* code */
		str_alloc.destroy(--pdel);	//销毁对象
	}
	str_alloc.deallocate(p,5);		//释放内存
	//
	Vector<int> i_vec;
	std::vector<int> i_vec_std;

	i_vec.push_back(10);
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";

	i_vec_std.push_back(10);
	std::cout << "i_vec_std.size() = " << i_vec_std.size() << "\n";
	std::cout << "i_vec_std.capacity() = " << i_vec_std.capacity() << "\n";

	for(int i=0; i<5; i++)
	{
		i_vec.push_back(i);
		i_vec_std.push_back(i);
	}
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";

	std::cout << "i_vec_std.size() = " << i_vec_std.size() << "\n";
	std::cout << "i_vec_std.capacity() = " << i_vec_std.capacity() << "\n";
	//move_back测试
	i_vec.move_back();
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";
	i_vec.move_back();
	std::cout << "i_vec.size() = " << i_vec.size() << "\n";
	std::cout << "i_vec.capacity() = " << i_vec.capacity() << "\n";
	//拷贝构造测试
	Vector<int> i_vec_cpy(i_vec);
	std::cout << "i_vec_cpy.size() = " << i_vec_cpy.size() << "\n";
	std::cout << "i_vec_cpy.capacity() = " << i_vec_cpy.capacity() << "\n";
	//赋值测试
	Vector<int> i_vec_opt = i_vec;
	std::cout << "i_vec_opt.size() = " << i_vec_opt.size() << "\n";
	std::cout << "i_vec_opt.capacity() = " << i_vec_opt.capacity() << "\n";
	//仿迭代器遍历
	Vector<int>::iterator iter = i_vec.begin();
	while (iter!=i_vec.end())
	{
		std::cout << "i_vec::iter = " << *iter << "\n";
		iter++;
	}
	//pop_back测试
	int ival = 0;
	int size = (int)i_vec.size();
	for(int i=0; i<size; i++)
	{
		i_vec.pop_back(ival);
		std::cout << "ival = " << ival << "\n";
	}
	// 自定义类的operator new、operator delete成员
	DeriveClass *ptest = new DeriveClass();
	strcpy(ptest->pc,"hello");
	std::cout << "ptest->pc = " << std::string(ptest->pc) << "\n";
	delete ptest;
	ptest = NULL;
	//标准库的operator new、operator delete 函数
	DeriveClass *ptest_g = ::new DeriveClass();
	strcpy(ptest_g->pc,"hello");
	std::cout << "ptest_g->pc = " << std::string(ptest_g->pc) << "\n";
	::delete ptest_g;
	ptest_g = NULL;
	//
	std::cout << "sizeof(MyProtocol) = " << sizeof(MyProtocol)  << "\n";
	MyProtocol mypr_;
	mypr_.mode = 1;
	std::cout << "mypr_.mode = " << mypr_.mode  << "\n";
	mypr_.mode = (Bit)10;//1010
	std::cout << "mypr_.mode = " << mypr_.mode  << "\n";	//取两位即10
	mypr_.set_world1(1);
	std::cout << "mypr_.get_world1 = " << mypr_.get_world1()  << "\n";
	mypr_.set_world1(10);//00001010
	std::cout << "mypr_.get_world1 = " << mypr_.get_world1()  << "\n";	//取6位即001010
	//
	mypr_.mode = MyProtocol::MODE2 ;
	std::cout << "mypr_.mode = " << mypr_.mode  << "\n";
	//
	u_val uv_;
	double dv_;
	std::cout << "sizeof(u_val) = " << sizeof(u_val)  << "\n";
	std::cout << "sizeof(uv_) = " << sizeof(uv_)  << "\n";
	std::cout << "sizeof(dv_) = " << sizeof(dv_)  << "\n";
	//
	std::cout << "uv_.c_val = " << uv_.c_val << "\n";
	std::cout << "uv_.i_val = " << uv_.i_val << "\n";
	std::cout << "uv_.d_val = " << uv_.d_val << "\n";
	std::cout << "uv_.ul_val = " << uv_.ul_val << "\n";
	//
	u_val uv_c = {'a'};
	std::cout << "uv_c.c_val = " << uv_c.c_val << "\n";
	uv_c = {100};
	std::cout << "uv_c.i_val = " << uv_c.i_val << "\n";
	//
	std::cout << "sizeof(i_union) = " << sizeof(i_union)  << "\n";
	i_union iu_;//error 无法得到i_union默认构造函数
	std::cout << "iu_.puc->getVal() = " << iu_.puc->getVal()  << "\n";
	//std::cout << "iu_.uc.getVal() = " << iu_.uc.getVal()  << "\n";	//这可能是灾难
	std::cout << "iu_.uval = " << iu_.uval  << "\n";
	//
	UClass uc_test;
	uc_test = 100;
	switch (uc_test.tid) {
		case UClass::INT:
			std::cout << uc_test.val.ival <<"\n"; break;
		case UClass::CHAR:
			std::cout << uc_test.val.cval <<"\n"; break;
		case UClass::DBL:
			std::cout << uc_test.val.dval  <<"\n"; break;
		default: break;
	}
	return 0;
}

        Vector.h

#ifndef _VECTOR_H_
#define _VECTOR_H_

#include <memory>

template <typename T> 
class Vector 
{
public:
    Vector(): elements(0), first_free(0), ends(0) { }
    Vector(const Vector&);
    Vector &operator=(const Vector&);
    ~Vector();
    void push_back(const T&);
    void pop_back(T& val);
    void move_back();
    std::size_t size() const;                 //Vector 的 size(实际使用的元素的数目)等于 first_free-elements
    std::size_t capacity() const;             //Vector 的 capacity(在必须重新分配 Vector 之前,可以定义的元素的总数)等于 end-elements
    std::size_t freeSize() const;             //自由空间(在需要重新分配之前,可以增加的元素的数目)是end-first_free

    typedef T   value_type;
    typedef value_type* iterator;
        
    iterator begin() const;
    iterator end() const;
private:
    std::pair<T*, T*> alloc_n_copy(const T *, const T *);
    void reallocate();              // get more space and copy existing elements
    void free();
private:
    static std::allocator<T> alloc;        // object to get raw memory
    T* elements;                    // 指向数组的第一个元素
    T* first_free;                  // 向最后一个实际元素之后的那个元素
    T* ends;                        // 指向数组本身之后的那个元素
};

#include "Vector.cpp"
#endif //_VECTOR_H_

        Vector.cpp

#include "Vector.h"

#ifdef __linux__
#include <stdio.h>
#endif

template <typename T>
std::allocator<T> Vector<T>::alloc;

//拷贝构造函数
template <typename T>
Vector<T>::Vector(const Vector<T> &strtmp)
{
    //将形参数据拷贝给自己,std::pair<T*, T*> rsp
    std::pair<T*, T*> rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, ends,first_free
    elements = rsp.first;
    first_free = rsp.second;
    ends = rsp.second;
}

//拷贝赋值运算符
template <typename T>
Vector<T>& Vector<T>::operator=(const Vector<T> &strtmp)
{
    //防止自赋值
    if (this == &strtmp)
    {
        return *this;
    }
    //将形参数据拷贝给自己,std::pair<T*, T*> rsp
    std::pair<T*, T*> rsp = alloc_n_copy(strtmp.begin(), strtmp.end());
    //更新elements, cap,first_free
    elements = rsp.first;
    first_free = rsp.second;
    ends = rsp.second;
}

//析构
template <typename T>
Vector<T>::~Vector()
{
    free();
}

template <typename T>
void Vector<T>::push_back(const T& t)
{
    // 是否有可用剩余空间?
    if (first_free == ends)
        reallocate();     // 分配新空间并复制现存元素,将指针重置为指向新分配的空间
    //alloc.construct(first_free++, t);
    alloc.construct(first_free, t);//就请求 allocator 对象构造一个新的最后元素,construct 函数使用类型 T 的复制构造函数将 t 值复制到由 first_free 指出的元素
    ++first_free;       //将 first_free 加 1 以指出又有一个元素在用
}

template <typename T>
void Vector<T>::pop_back(T& val)
{
    if(NULL==first_free) return;
    if(first_free!=elements)
    {
        val = *(--first_free);
        alloc.destroy(first_free); //删除了对象,但不释放空间
    }else{
        val = *(elements);
        alloc.destroy(elements);    //删除了对象,但不释放空间
        first_free = NULL;
        elements = NULL;
    }
}

template <typename T>
void Vector<T>::move_back()
{
    if(NULL==first_free) return;
    if(first_free!=elements)
    {
        alloc.destroy(--first_free); //删除了对象,但不释放空间
    }else{
        alloc.destroy(first_free);   //删除了对象,但不释放空间
        first_free = NULL;
        elements = NULL;
    }
}

template <typename T> 
void Vector<T>::reallocate()
{
    std::ptrdiff_t size = first_free - elements;  //计算当前在用的元素数目
    std::ptrdiff_t newcapacity = 2 * std::max((int)size, 1);//每次重新分配时分配两倍内存,按需要扩展
    /*请求 allocator 对象来获得所需数量的空间
    *如果 Vector 保存 int 值,allocate 函数调用为 newcapacity 数目的int 值分配空间;
    *如果 Vector 保存 string 对象,它就为给定数目的 string对象分配空间。
    */
    T* newelements = alloc.allocate(newcapacity);
    //标准 copy 算法的特殊版,拷贝构造旧数据到新存储空间
    std::uninitialized_copy(elements, first_free, newelements);
    //一旦复制和撤销了元素,就释放原来数组所用的空间
    free();
    elements = newelements;//重置指针以指向新分配并初始化的数
    //将 first_free 和 ends指针分别置为指向最后构造的元素之后的单元以及所分配空间末尾的下一单元.
    first_free = elements + size;
    ends = elements + newcapacity;
}

template <typename T> 
std::pair<T*, T*> Vector<T>::alloc_n_copy(const T *b, const T *e)
{
    T* newdata = alloc.allocate(e - b);
    //将原数据用来初始化新空间
    T* first_free = std::uninitialized_copy(b, e, newdata);
    return {newdata, first_free};
}

template <typename T> 
void Vector<T>::free()
{
    for (T *p = first_free; p != elements; /* empty */ )
    {
        alloc.destroy(--p); //删除了对象,但不释放空间
    }
    if (elements)//检查 elements 是否实际指向一个数组。
    {
        // return the memory that held the elements
        alloc.deallocate(elements, ends - elements);//指向由allocate分配的空间的指针
    }
}

template <typename T> 
std::size_t Vector<T>::size() const
{
    std::size_t size_ = (std::size_t)(first_free-elements);
    return size_;
};

template <typename T> 
std::size_t Vector<T>::capacity() const
{
    std::size_t capacity_ = (std::size_t)(ends-elements);
    return capacity_;
}

template <typename T> 
std::size_t Vector<T>::freeSize() const
{
    std::size_t freeSize_ = (std::size_t)(ends-first_free);
    return freeSize_;
}
//
template <typename T> 
typename Vector<T>::iterator Vector<T>::begin() const
{
    return elements;
}

template <typename T> 
typename Vector<T>::iterator Vector<T>::end() const
{
    return first_free;
}

        BaseObj.h

#ifndef _BASE_OBJ_H_
#define _BASE_OBJ_H_

#include <memory>

template <typename T> 
class BaseObj 
{
public:
    void *operator new(std::size_t);
    void operator delete(void *, std::size_t);
    // void *operator new[](std::size_t);
    // void operator delete[](void *, std::size_t);
    virtual ~BaseObj() { }
protected:
    T *next;
private:
    static void add_to_freelist(T*);
    static std::allocator<T> alloc_mem;
    static T *freeStore;
    static const std::size_t chunk;
};

#include "BaseObj.cpp"
#endif //_BASE_OBJ_H_

        BaseObj.cpp

#include "BaseObj.h"

#include <iostream>
#ifdef __linux__
#include <stdio.h>
#include <exception>
#endif
//
template <typename T> 
std::allocator<T> BaseObj<T>::alloc_mem;

template <typename T> 
T *BaseObj<T>::freeStore = 0;

template <typename T> 
const std::size_t BaseObj<T>::chunk = 24;

//operator new 使用这个函数将新分配的对象放到自由列表,删除对象
template <typename T>
void *BaseObj<T>::operator new(size_t sz)
{
    std::cout << "BaseObj operator new func is call!\n";
    // new should only be asked to build a T, not an object
    // derived from T; check that right size is requested
    if (sz != sizeof(T))
    {
        #ifdef WIN32
        throw std::runtime_error("BaseObj: wrong size object in operator new");
        #else
        std::cout << "BaseObj: wrong size object in operator new\n";
        #endif
    }
    if (!freeStore) 
    {
        // the list is empty: grab a new chunk of memory
        // allocate allocates chunk number of objects of type T
        T * array = alloc_mem.allocate(chunk);
        // now set the next pointers in each object in the allocated memory
        for (size_t i = 0; i != chunk; ++i)
        {
            add_to_freelist(&array[i]);
        }
    }
    T *p = freeStore;
    freeStore = freeStore->BaseObj<T>::next;
    return p; // constructor of T will construct the T part of the object
}
//operator delete 也使用该函数将对象放回自由列表
template <typename T>
void BaseObj<T>::operator delete(void *p, size_t)
{
    std::cout << "BaseObj operator delete func is call!\n";
    if (p != 0){
        // put the "deleted" object back at head of freelist
        add_to_freelist(static_cast<T*>(p));
    }
}

template <typename T>
void BaseObj<T>::add_to_freelist(T *p)
{
    p->BaseObj<T>::next = freeStore;
    freeStore = p;
}

        test.h

#ifndef _TEST_H_
#define _TEST_H_

#include "BaseObj.h"

class DeriveClass: public BaseObj<DeriveClass>
{
	public:
	DeriveClass() : pc(NULL)
	{
		pc = new char[10];
	};
	~DeriveClass(){
		if(NULL!=pc)
		{
			delete pc;
			pc = NULL;
		}
	};
	char *pc;
};
//
typedef unsigned int Bit;
class MyProtocol {
public:
    enum { UNDEF=0, MODE1 = 0X01, MODE2 = 0X02, MODE3=0X03 }; //
    Bit mode: 2;        //模式00 01 10 11四种
    Bit modified: 1;    //读写0 1两种
    Bit pgroup: 3;      //组
    Bit pcode: 1;       //编码方式
    Bit pcheck: 1;      //是否校验
private:
    Bit pworld1: 6;      //内容1
    Bit pworld2: 6;      //内容2
    Bit pworld3: 6;      //内容3
    Bit pworld4: 6;      //内容4
    // ...
public:
    void set_world1(Bit val)
    {
        pworld1 = val;
    };
    Bit get_world1()
    {
        return pworld1;
    };
};
//
typedef union union_value {
    // union_value() : i_val(10) {};
    // ~union_value() {};
    char c_val;
    int i_val;
    double d_val;
    unsigned long ul_val;
}u_val;

class Utest
{
private:
    int val;
public:
    Utest(/* args */);
    ~Utest();
    Utest& operator=(const Utest&);
    int getVal();
};

Utest::Utest() : val(10){};
Utest::~Utest(){};
Utest& Utest::operator=(const Utest&rhs)
{
    if(this==&rhs)
        return *this;
    val = rhs.val;
    return  *this;
};
int Utest::getVal(){ return val; };

typedef union illegal_union {
    illegal_union(): puc(new Utest()) {};
    ~illegal_union(){
        if(NULL!=puc)
        {
            delete puc;
            puc = NULL;
        }
    };
    // Utest uc;           // error: has constructor
    // static int is;      // error: static member
    //int &rfi;           // error: reference member
    Utest *puc;         // ok: ordinary built-in pointer type
    int uval;           //
}i_union;

class UClass {
public:
    enum ValType {INT, CHAR, DBL};
    ValType tid;
    union { // unnamed union
        char cval;
        int ival;
        double dval;
    } val; // member val is a union of the 3 listed types
    UClass & operator=(const char &val_)
    {
        tid = CHAR;
        val.cval = val_;
        return *this;
    };
    UClass & operator=(const int &val_)
    {
        tid = INT;
        val.ival = val_;
        return *this;
    };
    UClass & operator=(const double &val_)
    {
        tid = DBL;
        val.dval = val_;
        return *this;
    };
};

//#include "test.cpp"
#endif //_TEST_H_

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370939.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Git】Git冲突与解决方法

目录 一、Git冲突如何产生&#xff1f; 二、解决Git冲突—手动修改冲突 【第一步】在 hot-fix 分支上增加如下代码&#xff0c;并且提交。 【第二步】在master 分支上同样的地方增加如下代码&#xff0c;并且提交。 【第三步】 我们现在在 master 分支上合并 hot-fix 分支&a…

慢雾:Discord 私信钓鱼手法分析

事件背景 5 月 16 日凌晨&#xff0c;当我在寻找家人的时候&#xff0c;从项目官网的邀请链接加入了官方的 Discord 服务器。在我加入服务器后立刻就有一个"机器人"(Captcha.bot)发来私信要我进行人机验证。这一切看起来相当的合理。我也点击了这个验证链接进行查看…

数据结构——顺序表讲解

作者&#xff1a;几冬雪来 时间&#xff1a;2023年2月25日 内容&#xff1a;数据结构顺序表内容讲解 目录 前言&#xff1a; 顺序表&#xff1a; 1.线性表&#xff1a; 2.什么是顺序表&#xff1a; 3.顺序表的概念和构成&#xff1a; 4.顺序表的书写&#xff1a; 1…

【Web逆向】万方数据平台正文的逆向分析(上篇--加密发送请求)—— 逆向protobuf

【Web逆向】万方数据平台正文的逆向分析&#xff08;上篇--加密发送请求&#xff09;—— 逆向protobuf声明一、了解protobuf协议&#xff1a;二、前期准备&#xff1a;二、目标网站&#xff1a;三、开始分析&#xff1a;我们一句句分析&#xff1a;先for循环部分&#xff1a;后…

Servlet笔记(11):Servletcontext对象

1、什么是ServletContext ServletContext是一个全局储存空间&#xff0c;随服务器的生命周期变化&#xff0c; Cookie&#xff0c;Session&#xff0c;ServletContext的区别 Cookie&#xff1a; 存在于客户端的本地文本文件 Session&#xff1a; 存在于服务器的文本文件&#…

今天我在朋友圈看到的新京报公众号一段文章&#xff1a;十三届全国人大&#xff08;过几天就任期结束&#xff09;在第五次会议&#xff08;2022年3月5日&#xff09;对证监会提了一条第6178号建议《关于严厉打击风水盲测股市动向的建议》。今天&#xff0c;证监会进行了收称答…

业务代码编写过程中如何「优雅的」配置隔离

思考 不同的处理方式 1.常规的处理方式&#xff0c;通过某种规则判断区分代码环境 // 获取环境标识 const env getCurrentEnv();if (env dev) {// do something } else if (env test) {// do something } else if (env prod) {// do something } 分析&#xff1a; 1.此种…

Linux 操作系统——查看/修改系统时区、时间、本地时间修改为UTC

文章目录1.背景描述2.知识储备3.解决步骤1. 查看当前时区2.修改设置Linux服务器时区3.复制相应的时区文件&#xff0c;替换系统时区文件&#xff1b;或者创建链接文件4. 查看和修改Linux的时间5. 硬件时间和系统时间的 相互同步1.背景描述 最近一个项目日期采用java8的LocalDa…

你在公司混的差,可能和组织架构有关!

原创&#xff1a;小姐姐味道&#xff08;微信公众号ID&#xff1a;xjjdog&#xff09;&#xff0c;欢迎分享&#xff0c;非公众号转载保留此声明。如果你接触过公司的面试工作&#xff0c;一定见过很多来自大公司的渣渣。这些人的薪资和职位&#xff0c;比你高出很多&#xff0…

Delphi RSA加解密(二)

dll开发环境: Delphi XE 10.1 Berlin exe开发环境: Delphi 6 前提文章: Delphi RSA加解密(一) 目录 1. 概述 2. 准备工作 2.1 下载DEMO程序 2.2 字符编码说明 3. Cryption.dll封装 3.1 接口概况 3.2 uPub.pas单元代码 3.3 uInterface.pas单元代码 3.4 特别注意 4. 主程序…

如何选择合适的数字隔离器

随着数字隔离器在工业和汽车应用中的日益普及&#xff0c;从众多可用选项中选择最适合您系统的器件可能会让人不知所措。除了这一挑战之外&#xff0c;大多数数字隔离器在设计时都考虑了特定的系统要求和应用&#xff0c;让您需要对无穷无尽的规格和功能进行分类&#xff0c;以…

C++ sting类(一)各类操作

1、 C语言中的字符串C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不太符合程序设计的思想&#xff0c;而且底层空间需要用户自己管…

NEXUS 6P刷机安装Edxposed

刷机 abd等工具下载&#xff1a; https://developer.android.com/studio/releases/platform-tools?hlzh-cn 下载后配置环境变量 镜像下载&#xff1a; https://developers.google.com/android/images?hlzh-cn#angler Magisk下载 GitHub - topjohnwu/Magisk: The Magic M…

基于BP神经网络的衣服识别,BP神经网络详细原理,BP神经网络图像识别神经网络案例之19

目标 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络的激活函数&#xff0c; BP神经网络的传递函数 数据 神经网络参数 基于BP神经网络 性别识别的MATLAB代码 效果图 结果分析 展望 背影 人靠衣服马靠鞍&#xff0c;通过穿衣可…

Matlab字符串相关操作-拼接、格式化

常见的有三种方法&#xff1a;向量拼接、strcat函数和sprintf函数1、向量拼接在matlab中字符串本质上也是一个向量&#xff0c;可以通过矩阵运算来实现字符串的拼接&#xff0c;这里随便输入两个字符串a1和b1&#xff0c;用矩阵形式进行拼接&#xff1a;a1 I love;b1 Matlab…

Web项目部署环境搭建:JDK + Tomcat + IDEA

Web项目部署环境搭建&#xff1a;JDK Tomcat IDEA1.java JDK1.1 下载安装1.2 配置环境变量1.3 检查安装成功2. TomCat2.1 下载安装2.2 配置环境变量2.3 检查安装成功3.IDEA3.1 下载安装3.2 永久可得3.3 IDEA部署Tomcat出来混总是要还的&#xff0c;记得大学本科四年&#xff…

Protobuf 逆向解析两种方法

Protobuf 逆向解析两种方法一、Protobuf 的特征二、解析.bin文件显示原始数据法一&#xff1a;用 blackboxprotobuf 模块解析法二&#xff1a;使用 protoc 解析工具一、Protobuf 的特征 案例网址 Protocol buffers是一种语言无关、平台无关、可扩展的序列化结构数据的方法&…

计算机网络 — UDP协议(看这一篇就可以

UDP协议UDP是传输层的重要协议之一&#xff0c;另一重要协议为TCP协议。两者对比&#xff1a;TCP协议复杂&#xff0c;但传输可靠。UDP协议简单&#xff0c;但传输不可靠。UDP协议全称为&#xff1a;User Datagram Protocol&#xff08;用户数据报协议&#xff09;。它是一个简…

嵌入式 STM32 使用C语言打开读取歌曲目录

目录 一、C语言目录库函数 1、打开一个目录文件opendir() 2、readdir&#xff08;);读取目录下文件 3、建立链表保存读到的文件名 一、C语言目录库函数 目录库函数链接&#xff1a;http://wjhsh.net/qiny1012-p-8405394.html 1、打开一个目录文件opendir() opendir()函数…

JavaScript BOM操作

目录 前言 window 对象 location 对象 navigator 对象 screen 对象 history 对象 前言 BOM&#xff08;Browser Object Model&#xff09;指的是浏览器对象模型&#xff0c;它是 JavaScript 和浏览器之间的接口。通过 BOM&#xff0c;JavaScript 可以与浏览器窗口交互&…