r vector 4 elements_c++深入剖析之vector的Free store,init, Copy,Move

Vector不仅仅是最有用的标准容器。它还提供了最重要的implementation techniques的示例。

向量是最有用的容器
•简单
•简洁地存储给定类型的元素
•有效的访问 •扩展以容纳任意数量的元素
•可选的范围检查访问
这是怎么做到的呢?也就是说,向量是如何实现的?•我们将一个接一个地逐步回答这个问题
Vector是默认的容器,除非有很好的理由,否则最好使用vector来存储元素。
•硬件提供内存和地址
•低水平
•无类型
•固定大小的内存块
•没有检查
•As fast as the hardware architects can make it
•应用程序构建器需要类似向量的东西
•高级操作
•类型检查
•(随着数据的增多)大小不同
•运行时范围检查
•接近最佳速度
•在最底层,接近硬件的地方,生活简单而残酷
•你必须自己编写所有的程序
•你没有类型检查来帮助你
•当数据损坏或程序崩溃时,会发现运行时错误
•我们希望尽快达到更高的水平
•我们希望变得高效和可靠
•使用“适合人类”的语言
•斯特劳斯特鲁普《C++程序设计原理与实践》第17-19章基本上展示了所有需要的步骤
•理解的另一种选择是相信“魔法”
•构建向量的技术是所有高层数据结构工作的基础

•Vector

•可以容纳任意数量的元素
•可以处理任何物理内存和操作系统
•大小可能随时间而变化•例如,使用push_back()

vector age(4);
age[0]=.33; age[1]=22.0; age[2]=27.2; age[3]=54.2;

ba2001de25a22b95144de582d3a3da9a.png
// a very simplified vector of doubles (like vector<double>):
class vector {
int sz; // the number of elements (“the size”)
double* elem; // pointer to the first element
public:
vector(int s); // constructor: allocate s elements,
// let elem point to them,
// store s in sz
int size() const { return sz; } // the current size
};

Vector (constructor)

vector::vector(int s) // vector's constructor
:sz(s), // store the size s in sz
elem(new double[s]) // allocate s doubles on the free store
// store a pointer to those doubles in elem
{
}
// Note: new does not initialize elements (but the standard vector does)
注1:
计算机的内存,就像一个程序看到的那样:
•局部变量“活在堆栈上”
•全局变量是“静态数据”
•可执行代码在“代码部分”

8c77b9e34237f68f1d1d940562c5c142.png
注2:
•使用指针和数组,我们可以直接“接触”硬件,而只需语言提供很少的帮助。严重的编程错误最容易在这里发生,从而导致程序故障和模糊的bug。
•如果你得到"segmentation fault", "bus error", 或"core dumped",那么这很可能是指针未初始化或无效导致的。
•对比下来,向量灵活性高和性能好,并得到语言的更大支持(更少的错误和更少的调试时间)。

Vector (construction and primitive access)

// a very simplified vector of doubles:
class vector {
int sz; // the size
double* elem; // a pointer to the elements
public:
vector(int s) :sz(s), elem(new double[s]) { } // constructor
double get(int n) const { return elem[n]; } // access: read
void set(int n, double v) { elem[n]=v; } // access: write
int size() const { return sz; } // the current size
};
vector v(10);
for (int i=0; i<v.size(); ++i) { v.set(i,i); cout << v.get(i) << ' '; }

f8db445411bafe8f3feb7594d0a49210.png

问题:内存泄漏

double* calc(int result_size, int max)
{
double* p = new double[max]; // allocate another max doubles
// i.e., get max doubles from the free store
double* result = new double[result_size];
// … use p to calculate results to be put in result …
return result;
}
double* r = calc(200,100); // oops! We “forgot” to give the memory
// allocated for p back to the free store

•内存泄漏可能是现实程序中的一个严重问题
•必须长时间运行的程序承受不起任何内存泄漏

double* calc(int result_size, int max)
{
double* p = new double[max]; // allocate another max doubles
// i.e., get max doubles from the free store
double* result = new double[result_size];
// … use p to calculate results to be put in result …
delete[ ] p; // de-allocate (free) that array
// i.e., give the array back to the free store
return result;
}
double* r = calc(200,100);
// use r
delete[ ] r; // easy to forget

•一个需要“永远”运行的程序承受不起任何内存泄漏
•操作系统是程序“永远运行”的一个例子
•如果一个函数每次调用都会泄漏8字节,那么在它泄漏/丢失1兆字节之前,它可以运行多少天?
Tricky问题:没有足够的数据来回答,但大约有13万个calls
•在程序结束时,所有内存返回给系统
• Program that runs to completion with predictable memory usage may leak without causing problems。即:内存泄漏并不是“好/坏”的,但是在特定的情况下,它们可能是主要的困扰。

另一种内存泄漏的方式

void f()
{
double* p = new double[27];
// …
p = new double[42];
// …
delete[] p;
}
// 1st array (of 27 doubles) leaked

我们如何系统地、简单地避免内存泄漏?

•不要直接修改“new”和“delete”
•使用向量等。
•或者使用垃圾收集器
垃圾收集器是一个程序,它跟踪您的所有分配,并将未使用的空闲存储分配的内存返回给空闲存储。不幸的是,即使垃圾收集器也不能防止所有的泄漏。

void f(int x)
{
vector v(x);// define a vector
// (which allocates x doubles on the free store)
// … use v …
// give the memory allocated by v back to the free store
// but how? (vector's elem data member is private)
}

Vector (destructor)

// a very simplified vector of doubles:
class vector {
int sz; // the size
double* elem; // a pointer to the elements
public:
vector(int s) // constructor: allocates/acquires memory
:sz(s), elem(new double[s]) { }
~vector() // destructor: de-allocates/releases memory
{ delete[ ] elem; }
// …
};

•注意:这是一个普遍而重要的技巧范例:

在构造函数中获取资源
在析构函数中释放它们
•资源示例:内存、文件、锁、线程、套接sockets

void f(int x)
{
int* p = new int[x]; // allocate x ints
vector v(x); // define a vector (which allocates another x ints)
// … use p and v …
delete[ ] p; // deallocate the array pointed to by p
// the memory allocated by v is implicitly deleted here by vector's destructor
}

•delete现在看起来冗长和丑陋
•我们如何避免忘记delete[]p? (经验表明我们常常忘记)
•我们更喜欢在析构函数中delete

Free store总结

•分配使用new
new在空闲存储区分配一个对象,有时会初始化它,并返回指向它的指针

 int* pi = new int; // default initialization (none for int)
• char* pc = new char('a'); // explicit initialization
• double* pd = new double[10]; // allocation of (uninitialized) array

New throws a bad_alloc exception if it can't allocate (out of memory)

•使用delete和delete[]来释放
delete和delete[]将new分配给空闲存储的对象的内存返回给Free store,以便Free store可以使用它进行新的分配

• delete pi; // deallocate an individual object
• delete pc; // deallocate an individual object
• delete[ ] pd; // deallocate an array

删除零值指针(“空指针”)不做任何事

 char* p = 0; // C++11 would say char* p = nullptr;
• delete p; // harmless

Vector: Initialization, Copy and Move

Initialization: initializer lists

我们想要简单、通用、灵活的初始化,所以我们提供了合适的构造函数,包括

class vector {
// …
public:
vector(int s); // constructor (s is the element count)
vector(std::initializer_list<double> lst); // initializer-list constructor
// …
};
vector v1(20); // 20 elements, each initialized to 0
vector v2 {1,2,3,4,5}; // 5 elements: 1,2,3,4,5
vector::vector(int s) // constructor (s is the element count)
:sz{s}, elem{new double[s]} { }
{
for (int i=0; i<sz; ++i) elem[i]=0;
}
vector::vector(std::initializer_list<double> lst) // initializer-list constructor
:sz{lst.size()}, elem{new double[sz]} { }
{
std::copy(lst.begin(),lst.end(),elem); // copy lst to elem
}
vector v1(20); // 20 elements, each initialized to 0
vector v2 {1,2,3,4,5}; // 5 elements: 1,2,3,4,5

初始化:列表和大小

•如果我们初始化一个向量是17
17个元素(值为0)?
1个值为17的元素?
•按照惯例使用
()表示元素的数量
{}为元素

• vector v1(17); // 17 elements, each with the value 0
• vector v2 {17}; // 1 element with value 17

一个问题--copy不像我们预期的那样工作

void f(int n)
{
vector v(n); // define a vector
vector v2 = v; // what happens here?
// what would we like to happen?
vector v3;
v3 = v; // what happens here?
// what would we like to happen?
// …
}

•理想情况下:v2和v3成为v的copies(也就是说,=制造copies),从f()退出时,所有内存都返回到free store

默认的naive copy初始化(By default “copy” means “copy the data members” )

15024640cc53acf3267e3fbee73ed288.png

当我们离开f(),v1的元素被删除两次(被析构函数删除)!

4082663f4e32b75c1cc8f794cf29390d.png

①v1的元素被删除两次(通过析构函数)②内存泄漏:v2的元素没有被删除

Copy constructor (initialization)

class vector {
int sz;
double* elem;
public:
vector(const vector&) ; // copy constructor: define copy (below)
// …
};
vector::vector(const vector& a)
:sz{a.sz}, elem{new double[a.sz]}
// allocate space for elements, then initialize them (by copying)
{
for (int i = 0; i<sz; ++i) elem[i] = a.elem[i];
}

void f(int n)
{
vector v1(n);
vector v2 = v1; // copy using the copy constructor
// the for loop copies each value from v1 into v2
}

Copy assignment

vector& vector::operator=(const vector& a)
// like copy constructor, but we must deal with old elements
// make a copy of a then replace the current sz and elem with a’s
{
double* p = new double[a.sz]; // allocate new space
for (int i = 0; i<a.sz; ++i) p[i] = a.elem[i]; // copy elements
delete[ ] elem; // deallocate old space
sz = a.sz; // set new size
elem = p; // set new elements
return *this; // return a self-reference
// The this pointer is explained in 17.10
}

63e26a9d0edffc620bbc24d251ad21f2.png

浅拷贝:只拷贝一个指针,这样两个指针就会指向同一个对象(What pointers and references do)
•深拷贝:两个指针就会分别指向不同的对象
(What vector, string, etc. do)
对于容器类需要copy constructors和copy assignments
如果对象中有更多级别,则必须copy“all the way down”

vector<int> v1 {2,4};
vector<int> v2 = v1; // deep copy (v2 gets its own copy of v1’s elements)
v2[0] = 3; // v1[0] is still 2

int b = 9; 
int& r1 = b;
int& r2 = r1; // shallow copy (r2 refers to the same variable as r1)
r2 = 7; // b becomes 7

Move

vector fill(istream& is)
{
vector res;
for (double x; is>>x; ) res.push_back(x);
return res; // returning a copy of res could be expensive
// returning a copy of res would be silly!
}
void use()
{
vector vec = fill(cin);
// … use vec …
}

What we want: Move

96d88a63dd3479f891e9cbef27c5ba7a.png

Move Constructor and assignment

• Define move operations to “steal” representation(&& indicates “move”)

lass vector {
int sz;
double* elem;
public:
vector(vector&&); // move constructor: “steal” the elements
vector& operator=(vector&&); // move assignment:
// destroy target and “steal” the elements
// . . .
}; 

Move implementation

vector::vector(vector&& a) // move constructor
:sz{a.sz}, elem{a.elem} // copy a’s elem and sz
{
a.sz = 0; // make a the empty vector
a.elem = nullptr;
}

改进

vector& vector::operator=(vector&& a) // move assignment
{
delete[] elem; // deallocate old space
elem = a.elem; // copy a’s elem and sz
sz = a.sz;
a.elem = nullptr; // make a the empty vector
a.sz = 0;
return *this; // return a self-reference (see §17.10)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值