一. 学习中遇到的问题
1. forward和move
1.1. move
假如有一个class A, 带有一个set函数, 可以传两个参数赋值class里的成员变量:
class A{
...};
void A::set(const string & var1, const string & var2){
m_var1 = var1; //copy
m_var2 = var2; //copy
}
下面这个写法是没法避免copy的, 因为怎么着都得把外部初始的string传进set函数, 再复制给成员变量:
A a1;
string var1("string1");
string var2("string2");
a1.set(var1, var2); // OK to copy
但下面这个呢? 临时生成了2个string, 传进set函数里, 复制给成员变量, 然后这两个临时string再被回收.
A a1;
a1.set("temporary str1","temporary str2"); //temporary, unnecessary copy
上面复制的行为, 在底层的操作很可能是这样的:
(1)临时变量的内容先被复制一遍
(2)被复制的内容覆盖到成员变量指向的内存
(3)临时变量用完了再被回收
这里能不能优化一下呢? 临时变量反正都要被回收, 如果能直接把临时变量的内容, 和成员变量内容交换一下, 就能避免复制了? 如下:
(1)成员变量内部的指针指向"temporary str1"所在的内存
(2)临时变量内部的指针指向成员变量以前所指向的内存
(3)最后临时变量指向的那块内存再被回收
上面这个操作避免了一次copy的发生, 其实它就是所谓的move语义.
为了解决这个问题,于是引入了左值和右值的概念,之前说的临时变量就是右值,,上面所说避免copy的操作就是std::move
。
再回到我们的例子:
没法避免copy操作的时候, 还是要用const T &把变量传进set函数里, 现在T &叫lvalue reference(左值引用)了, 如下:
void set(const string & var1, const string & var2){
m_var1 = var1; //copy
m_var2 = var2; //copy
}
A a1;
string var1("string1");
string var2("string2");
a1.set(var1, var2); // OK to copy
传临时变量的时候, 可以传T &&, 叫rvalue reference(右值引用), 它能接收rvalue(临时变量), 之后再调用std::move就避免copy了.
void set(string && var1, string && var2){
//avoid unnecessary copy!
m_var1 = std::move(var1);
m_var2 = std::move(var2);
}
A a1;
//temporary, move! no copy!
a1.set("temporary str1","temporary str2");
新的问题: 避免重复
1.2. forward
上面说的各种情况, 包括传const T &, T &&, 都可以由以下操作代替:
template<typename T1, typename T2>
void set(T1 && var1, T2 && var2){
m_var1 = std::forward<T1>(var1);
m_var2 = std::forward<T2>(var2);
}
//当var1是一个右值, std::forward<T1> 等价于 static_cast<[const] T1 &&>(var1)
//当var1是一个左值, std::forward<T1> 等价于 static_cast<[const] T1 &>(var1)
forward能转发以下情况:
const T &
T &
const T &&
T &&
如果外面传来了rvalue临时变量, 它就转发rvalue并且启用move语义.
如果外面传来了lvalue, 它就转发lvalue并且启用复制. 然后它也还能保留const.
2. new
2.1 ::new 和 new
在全局命名空间中有一个自带的、隐藏的operator new专门用来分配内存。默认情况下编译器会将new这个关键字翻译成这个operator new和相应的构造函数。
但在有的情况下,用户自己会在类中重载operator new,这种情况下,编译器默认会使用类中重载的operator new(本质上因为编译器会从命名空间由内而外查找自己想要的函数,选用第一个)。
如果我们想要继续使用默认的operator new,就应该写成::new 字面意思就是调用最外层命名空间中的operator new
2.2 placement new
new,先分配内存,后调构造函数。那么,可不可以保持一块内存,反复构造析构,这样可以省略中间的多次分配内存。由于malloc内存会导致系统调用(malloc()之后,内核发生了什么),这可以节省大量的系统开销。C++标准也是这么想的,所以他们提供了placement new。
char* ptr = new char[sizeof(T)]; // allocate memory
T* tptr = new(ptr) T; // construct in allocated storage ("place")
tptr->~T(); // destruct
delete[] ptr; // deallocate memory
二. MyTinySTL分配器
涉及construct.h、allocator.h文件
首先对于C++中的delete和new函数进行一些解释:
在我们使用new来建造一个对象的时候,一般是分为两个步骤的:
1.调用::operator new()来构建空间;
2.调用对象的构造函数。
注:::operator new内部由malloc实现、省略指针转换这步。
同理,当我们使用delete函数的时候:
1.调用对象的析构函数
2.调用::operator delete()释放空间。
注:::operator delete内部由free实现
在STL的实现中,为了精细分工、STL allocator决定将上述步骤的两阶段操作分开来,内存
配置操作由alloc::allocate()负责,内存释放由alloc::deallocate负责,对象构建
操作由construct负责,对象析构操作由destroy负责。接下来我们梳理内存配置器的具体
实现流程。
construct
使用placement new来进行构造;这是operator new的一个重载版本、允许你在一个已经分配好的内存中(栈或堆中)构造一个新的对象,其中的void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
template <class Ty>
void construct(Ty *ptr)
{
::new ((void*)ptr) Ty();
}
template <class Ty1, class Ty2>
void construct(Ty1* ptr, const Ty2& value)
{
::new ((void*)ptr) Ty1(value);
}
template <class Ty, class... Args>
void construct(Ty* ptr, Args&&... args)
{
::new ((void*)ptr) Ty(mystl::forward<Args>(args)...);
}
destory
我们传入的是一个指针的时候,会调用destroy_one()函数,当我们发现传入的析构函数是重要的时候、我们调用destroy_one()函数的false_type特化版本,直接调用析构函数即可;
当传入两个迭代器的时候,如果发现是重要的析构函数,那么我们对于每个迭代器调用destroy()函数,此时传入的是当前迭代器,相应的调用的就会是之前所说的destroy_one的特化的版本对对象进行析构。
template <class Ty>
void destroy_one(Ty*, std::true_type) {
}
template <class Ty>
void destroy_one(Ty* pointer, std::false_type)
{
if (pointer != nullptr)
{
pointer->~Ty();
}
}
template <class ForwardIter>
void destroy_cat(ForwardIter, ForwardIter, std::true_type) {
}
template <class ForwardIter>
void destroy_cat(ForwardIter first, ForwardIter last, std::false_type)
{
for(; first != last; ++first)
destroy(&*first);
}
/*
std::is_trivially_denstructible模板用于检查T是否是普通可破坏类型。
如果T是普通可破坏类型,则返回布尔值true;否则返回false。
*/
template <class Ty>
void destroy(Ty* pointer)
{
destory_one(pointer, std::is_trivially_destructible<Ty> {
});
}
template <class ForwardIter>
void destroy(ForwardIter first, ForwardIter last)
{
destroy_cat(first, last, std::is_trivially_destructible<
typename iterator_traits<ForwardIter>::value_type>{
});
}
allocator
类
template <class T>
class allocator
{
public:
typedef T value_type; /*! 数据类型 */
typedef T* pointer; /*! 数据类型指针 */
typedef const T* const_pointer; /*! const数据类型指针 */
typedef T& reference; /*! 数据类型引用 */
typedef const T& const_reference; /*! const数据类型引用 */
typedef size_t size_type; /*! 数据类型大小 */
typedef ptrdiff_t difference_type; /*! 数据类型指针距离 */
public:
// 分配内存
static T* allocate();
static T* allocate(size_type n);
// 回收内存
static void deallocate(T* ptr);
static void deallocate(T* ptr, size_type n);
// 构造对象
static void construct(T* ptr);
static void construct(T* ptr, const T& value);
static void construct(T* ptr, T&& value);
template <class... Args>
static void construct(T* ptr, Args&& ...args);
// 析构对象
static void destroy(T* ptr);
static void destroy(T* first, T* last);
};
allocator实现
template <class T>
T* allocator<T>::allocate()
{
return static_cast<T*>(::operator new(sizeof(T)));
}
template <class T>
T* allocator<T>::allocate(size_type n)
{
if (n == 0)
return nullptr;
return static_cast<T*>(::operator new(n * sizeof(T)));
}
deallocate
template <class T>
void allocator<T>::deallocate(T* ptr)
{
if (ptr == nullptr)
return;
::operator delete(ptr);
}
template <class T>
void allocator<T>::deallocate(T* ptr, size_type /*size*/)
{
if (ptr == nullptr)
return;
::operator delete(ptr);
}
construct
template <class T>
void allocator<T>::construct(T* ptr)
{
mystl::construct(ptr);
}
template <class T>
void allocator<T>::construct(T* ptr, const T& value)
{
mystl::construct(ptr, value);
}
template <class T>
void allocator<T>::construct(T* ptr, T&& value)
{
mystl::construct(ptr, mystl::move(value));
}
template <class T>
template<class ...Args>
void allocator<T>::construct(T* ptr, Args&&... args)
{
mystl::construct(ptr, mystl::forward<Args>(args)...);
}
destory
template <class T>
void allocator<T>::destroy(T* ptr)
{
mystl::destroy(ptr);
}
template <class T>
void allocator<T>::destroy(T* first, T* last)
{
mystl::destroy(first, last);
}
参考:
深入解析new、operator new、::new、placement new
move和forward
STL源码剖析:1.空间配置器
MyTinySTL阅读笔记—分配器