Urho3D自己实现了一套常用的容器类型,包括Vector
, List
, HashMap
及其相关类型。 容器类代码全部分布在SourceUrho3DContainer
下, 这里的代码可以作为我们分析学习Orho3D源码入手的切入点。
VectorBase
class URHO3D_API VectorBase
{
public:
/// Construct.
VectorBase() noexcept :
size_(0),
capacity_(0),
buffer_(nullptr)
{
}
/// Swap with another vector.
void Swap(VectorBase& rhs)
{
Urho3D::Swap(size_, rhs.size_);
Urho3D::Swap(capacity_, rhs.capacity_);
Urho3D::Swap(buffer_, rhs.buffer_);
}
protected:
static unsigned char* AllocateBuffer(unsigned size);
/// Size of vector.
unsigned size_;
/// Buffer capacity.
unsigned capacity_;
/// Buffer.
unsigned char* buffer_;
};
size_ 用来记录Vector中元素的个数, capacity_ 表示Vector可用容量 buffer_ Vector存放数据的内存地址
VectorBase提供的内部Swap函数会交换着三个成员变量的值。 Urho提供了全局的Swap模板函数,并且对一些类型提供了特化操作
template <class T> inline void Swap(T& first, T& second)
{
T temp = first;
first = second;
second = temp;
}
template <> void Swap<ListBase>(ListBase& first, ListBase& second)
{
first.Swap(second);
}
Vector
Vector
类相关的代码文件为:Vector.{h, cpp},继承了VectorBase
template <class T> class Vector : public VectorBase
Vector提供了与std::vector相似的方法,这里主要分析其中几个重要的成员函数的实现。
/// Resize the vector.
void Resize(unsigned newSize) { DoResize(newSize); }
/// Resize the vector and fill new elements with default value.
void Resize(unsigned newSize, const T& value)
{
unsigned oldSize = Size();
DoResize(newSize);
for (unsigned i = oldSize; i < newSize; ++i)
At(i) = value;
}
/// Resize the vector and create/remove new elements as necessary.
void DoResize(unsigned newSize)
{
// If size shrinks, destruct the removed elements
if (newSize < size_)
DestructElements(Buffer() + newSize, size_ - newSize);
else
{
// Allocate new buffer if necessary and copy the current elements
if (newSize > capacity_)
{
T* src = Buffer();
// Reallocate vector
Vector<T> newVector;
newVector.Reserve(CalculateCapacity(newSize, capacity_));
newVector.size_ = size_;
T* dest = newVector.Buffer();
// Move old elements
ConstructElements(dest, src, src + size_, MoveTag{});
Swap(newVector);
}
// Initialize the new elements
ConstructElements(Buffer() + size_, newSize - size_);
}
size_ = newSize;
}
这个方法用来动态调整Vector大小,Resize函数内部会转到DoResize函数来做真正的逻辑。 在DoResize中,当传入的参数newSize小于当前Vector的size时,会将多余的元素进行销毁(DestrutcElements); 当newSize的大小超过了Vector的容量(capacity_)时,会触发新的内存分配,并且会将原来的元素搬移(MoveTag)到新分配的内存中(ConstructElements), 当newSize大于现在Vector的size_, 而又没有超过capacity_时,会在原有Vector元素后面直接构造新的元素。
/// Call the elements' destructors.
static void DestructElements(T* dest, unsigned count)
{
while (count--)
{
dest->~T();
++dest;
}
}
DesctructElements是Vector的static成员函数,会在dest指定的内存位置处,调用count次元素T的析构函数。
static unsigned CalculateCapacity(unsigned size, unsigned capacity)
{
if (!capacity)
return size;
else
{
while (capacity < size)
capacity += (capacity + 1) >> 1;
return capacity;
}
}
静态成员函数CalculateCapacity会根据现有的capcity进行扩张(capacity += (capacity + 1) >> 1)来找到满足size大小的一个capacity的值。
static void ConstructElements(T* dest, unsigned count)
{
for (unsigned i = 0; i < count; ++i)
new(dest + i) T();
}
/// Copy-construct elements.
template <class RandomIteratorT>
static void ConstructElements(T* dest, RandomIteratorT start, RandomIteratorT end, CopyTag)
{
const unsigned count = end - start;
for (unsigned i = 0; i < count; ++i)
new(dest + i) T(*(start + i));
}
/// Move-construct elements.
template <class RandomIteratorT>
static void ConstructElements(T* dest, RandomIteratorT start, RandomIteratorT end, MoveTag)
{
const unsigned count = end - start;
for (unsigned i = 0; i < count; ++i)
new(dest + i) T(std::move(*(start + i)));
}
ConstructElements的三种重载。 第一种是没有Tag参数的,只是在dest指向的内存处执行了元素T的默认构造函数; 后面两种根据传入的Tag类型分别调用的了copy和move构造函数。
void Reserve(unsigned newCapacity)
{
if (newCapacity < size_)
newCapacity = size_;
if (newCapacity != capacity_)
{
T* newBuffer = nullptr;
capacity_ = newCapacity;
if (capacity_)
{
newBuffer = reinterpret_cast<T*>(AllocateBuffer((unsigned)(capacity_ * sizeof(T))));
// Move the data into the new buffer
ConstructElements(newBuffer, Begin(), End(), MoveTag{});
}
// Delete the old buffer
DestructElements(Buffer(), size_);
delete[] buffer_;
buffer_ = reinterpret_cast<unsigned char*>(newBuffer);
}
}
Reserve函数会重新调整Vector的容量大小。保证newCapacity的大小不小于Vector的size_。当newCapacity与当前的capacity_不相等时,调用VectorBase下的AllocateBuffer接口 来进行新的内存分配,大小为 newCapacity * sizeof(T)。得到新的内存地址之后,调用Move语义的ConstructElements函数来将原来内存中的数据进行搬移。(这里使用Move语言来触发新元素的移动构造函数,避免了内存拷贝带来的额外开销),之后会将原来旧的数据进行析构,并释放对应的内存空间。
/// Move-construct from another vector.
Vector(Vector<T> && vector)
{
Swap(vector);
}
/// Aggregate initialization constructor.
Vector(const std::initializer_list<T>& list) : Vector()
{
for (auto it = list.begin(); it != list.end(); it++)
{
Push(*it);
}
}
Vector提供了Move构造函数,内部实现时调用了针对Vector特化的Swap函数,新Vector会继续使用旧Vector的内存空间,不会有多余的内存分配和释放的操作。 Vector另外还提供了初始化列表,这样可以使用{}
来直接构造Vector的初始数据,内部调用了Push接口(该构造函数使用了默认构造函数Vector()来初始化Vector成员变量,c++11新语法支持)。
/// Insert elements into the vector using copy or move constructor.
template <class Tag, class RandomIteratorT>
Iterator DoInsertElements(unsigned pos, RandomIteratorT start, RandomIteratorT end, Tag)
{
if (pos > size_)
pos = size_;
const unsigned numElements = end - start;
if (size_ + numElements > capacity_)
{
T* src = Buffer();
// Reallocate vector
Vector<T> newVector;
newVector.Reserve(CalculateCapacity(size_ + numElements, capacity_));
newVector.size_ = size_ + numElements;
T* dest = newVector.Buffer();
// Copy or move new elements
ConstructElements(dest + pos, start, end, Tag{});
// Move old elements
if (pos > 0)
ConstructElements(dest, src, src + pos, MoveTag{});
if (pos < size_)
ConstructElements(dest + pos + numElements, src + pos, src + size_, MoveTag{});
Swap(newVector);
}
else if (numElements > 0)
{
T* buffer = Buffer();
// Copy or move new elements
ConstructElements(buffer + size_, start, end, Tag{});
// Rotate buffer
if (pos < size_)
{
std::rotate(buffer + pos, buffer + size_, buffer + size_ + numElements);
}
// Update size
size_ += numElements;
}
return Begin() + pos;
}
DoInsertElements接口会在pos处插入由迭代器start,end指定的一堆元素。当插入新元素的个数超出了Vector的容量值,会触发新的内存空间分配。 新的内存分配后,会在pos位置处,构造新的元素,之后会将就的Vector中Pos位置前后的元素搬移到新的Vector中。 如果Vector剩余的容量仍够放下新插入的元素,那么会先在Vector的尾部构造新的元素,之后调用std::rotate函数将原先pos之后的元素与新构造的元素进行置换。
void Push(const T& value)
{
if (size_ < capacity_)
{
// Optimize common case
++size_;
new (&Back()) T(value);
}
else
DoInsertElements(size_, &value, &value + 1, CopyTag{});
}
/// Move-add an element at the end.
void Push(T && value)
{
if (size_ < capacity_)
{
// Optimize common case
++size_;
new (&Back()) T(std::move(value));
}
else
DoInsertElements(size_, &value, &value + 1, MoveTag{});
}
template <class... Args> T& EmplaceBack(Args&&... args)
{
if (size_ < capacity_)
{
// Optimize common case
++size_;
new (&Back()) T(std::forward<Args>(args)...);
}
else
{
T value(std::forward<Args>(args)...);
Push(std::move(value));
}
return Back();
}
Vector的Push接口提供在尾部进行插入新元素的操作,分别提供了Copy和Move语义的接口。Emplace接口提供了在尾部构造新原色的接口,当Vector已满时,调用Push会触发内存重分配的操作。
/// Erase elements from the vector.
Iterator DoEraseElements(unsigned pos, unsigned count)
{
assert(count > 0);
assert(pos + count <= size_);
T* buffer = Buffer();
std::move(buffer + pos + count, buffer + size_, buffer + pos);
Resize(size_ - count);
return Begin() + pos;
}
DoEraseElements接口从pos处开始,删除count个元素。具体实现方式是通过std::move函数将pos+count以后的元素先搬移到pos处,然后调用Resize析构要删除的元素。 c++11提供的std::move接口会调用Move语义的operator=操作来交换元素。
PODVector
PODVector用来专门存放POD数据类型的元素,这些类型的元素的插入和删除不会触发对应的构造、析构函数。
void MoveRange(unsigned dest, unsigned src, unsigned count)
{
if (count)
memmove(Buffer() + dest, Buffer() + src, count * sizeof(T));
}
/// Copy elements from one buffer to another.
static void CopyElements(T* dest, const T* src, unsigned count)
{
if (count)
memcpy(dest, src, count * sizeof(T));
}
对应的MoveRange/CopyElements接口也只是直接调用了mem相关的函数。