Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~
关于生存期
Global objects
- allocated at program start-up
- destroyed when the program ends
Local, automatic objects
- created and destroyed when the block in which they are defined is entered and exited
Local static objects
- allocated before their first use
- destroyed when the program ends
Dynamically allocated objects
- have a lifetime that is independent of where they are created; they exist until they are explicitly freed
关于内存
概述
- Objects allocated in 静态内存和栈内存 are automatically created and destroyed by the compiler
- 动态内存指的是堆内存
静态内存
- 存储:local static objects、class static data members、variables defined outside any function
- 静态内存中的 objects are allocated before they are used, and they are destroyed when the program ends
栈内存
- 存储:nonstatic objects defined inside functions
- 栈内存中的 objects exist only while the block in which they are defined is executing
堆内存(free store)
- 存储:objects that they dynamically allocate,即objects that the program allocates at run time,由程序控制其生存期
- 使用动态内存的时机:don’t know how many objects will be needed; don’t know the precise type of the needed objects; want to share data between several objects
- Objects allocated on the free store are unnamed
直接管理动态内存
new
- 作用:allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object
- 初始化:默认情况下dynamically allocated objects are default initialized;a dynamically allocated const object must be initialized:对于const dynamic object of a class type that defines a default constructor,可以implicitly initialize,对于其他类型的对象,必须explicitly initialize
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(1024); // object to which pi points has value 1024
string *ps = new string(10, '9'); // *ps is "9999999999"
// vector with ten elements with values from 0 to 9
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
string *ps1 = new string; // default initialized to the empty string
string *ps = new string(); // value initialized to the empty string
int *pi1 = new int; // default initialized; *pi1 is undefined
int *pi2 = new int(); // value initialized to 0; *pi2 is 0
auto p1 = new auto(obj); // p points to an object of the type of obj;that object is initialized from obj
auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer
// allocate and initialize a const int
const int *pci = new const int(1024);
// allocate a default-initialized const empty string
const string *pcs = new const string;
- new失败
- Once a program has used all of its available free store memory, new will fail
- new失败会throw an exception of type bad_alloc
- 可以禁止throw bad_alloc:使用placement new,向new传参数
// if allocation fails, new returns a null pointer int *p1 = new int; // if allocation fails, new throws std::bad_alloc int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer
delete
- 作用:takes a pointer to a dynamic object, destroys that object, and frees the associated memory
- The pointer we pass to delete must either point to dynamically allocated memory or be a null pointer,否则结果未定义
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i; // error: i is not a pointer
delete pi1; // undefined: pi1 refers to a local
delete pd; // ok
delete pd2; // undefined: the memory pointed to by pd2 was already freed
delete pi2; // ok: it is always ok to delete a null pointer
- 可以delete const
const int *pci = new const int(1024);
delete pci; // ok: deletes a const object
- 使用普通指针而不是智能指针时,一定要记得delete!
// factory returns a pointer to a dynamically allocated object
Foo* factory(T arg) {
// process arg as appropriate
return new Foo(arg); // caller is responsible for deleting this memory
}
void use_factory(T arg) {
Foo *p = factory(arg);
// use p but do not delete it
} // p goes out of scope and is destroyed, but the memory to which p points is not freed!
- dangling pointer:delete一个指针后,它continues to hold the address of the (freed) dynamic memory,称为dangling pointer(悬空指针)
- 一定要记得把它设为空指针,或者等到它马上要go out of scope之前再delete
- 常见的bug:delete后产生悬空指针q
int *p(new int(42)); // p points to dynamic memory auto q = p; // p and q point to the same memory delete p; // invalidates both p and q p = nullptr; // indicates that p is no longer bound to an object
smart pointer
性质
- ensure that the objects to which they point are automatically freed when it is appropriate to do so
- 是c++ 11的新特性
- 三种智能指针(shared_ptr、unique_ptr、weak_ptr)都定义在头文件memory中
- 默认情况下a pointer used to initialize a smart pointer must point to dynamic memory,但只要我们supply our own operation to use in place of delete,也可以用指向其他资源的指针来初始化智能指针
struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */) {
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
// 或者
void f(destination &d /* other needed parameters */) {
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*> p (&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
PS:When p is destroyed, it won’t execute delete on its stored pointer. Instead, p will call end_connection on that pointer.
shared_ptr和unique_ptr都支持的操作
-
get()
- 作用:返回一个内置指针,指向智能指针的管理对象
- 使用场景:cases when we need to pass a built-in pointer to code that can’t use a smart pointer
- 注意危险行为:the code that uses the return from get must not delete that pointer;never use get to initialize or assign to another smart pointer
shared_ptr<int> p(new int(42)); // reference count is 1 int *q = p.get(); // ok: but don't use q in any way that might delete its pointer { // new block // undefined: two independent shared_ptrs point to the same memory shared_ptr<int>(q); } // block ends, q is destroyed, and the memory to which q points is freed int foo = *p; // undefined; the memory to which p points was freed
PS:因为p和q were created independently from each other,所以它们的reference count 都是1
shared_ptr
- 特点:允许多个指针指向同一对象
- 初始化
- 栗子
shared_ptr<string> p1; // shared_ptr that can point at a string shared_ptr<list<int>> p2; // shared_ptr that can point at a list of ints shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42 shared_ptr<int> p1 = new int(1024); // error: must use direct initialization
- 默认初始化的shared_ptr初始值为null pointer
- 智能指针的参数是指针的构造函数是explicit的,所以不能implicitly convert a built-in pointer to a smart pointer,只能使用direct initialization
shared_ptr<int> clone(int p) { return new int(p); // error: implicit conversion to shared_ptr<int> return shared_ptr<int>(new int(p)); // ok: explicitly create a shared_ptr<int> from int* }
- 只有shared_ptr支持的操作
-
make_shared
- 定义在头文件memory中
- 功能:allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object
- 特点:像emplace一样,make_shared uses its arguments to construct an object of the given type
- 栗子
// shared_ptr that points to an int with value 42 shared_ptr<int> p3 = make_shared<int>(42); // p4 points to a string with value 9999999999 shared_ptr<string> p4 = make_shared<string>(10, '9'); // p5 points to an int that is value initialized to 0 shared_ptr<int> p5 = make_shared<int>();
-
reset
- 作用:assign a new pointer to a shared_ptr, updates the reference counts and, if appropriate, deletes the object to which p points
- 栗子
p = new int(1024); // error: cannot assign a pointer to a shared_ptr p.reset(new int(1024)); // ok: p points to a new object if (!p.unique()) p.reset(new string(*p)); // we aren't alone; allocate a new copy *p += newVal; // now that we know we're the only pointer, okay to change this object
-
shared_ptr的复制和赋值
- reference count
- associates with a shared_ptr的指向对象,记录how many other shared_ptrs point to the same object
- 智能指针p指向对象的reference count++的情形:用p初始化另一个智能指针时、use it as the right -hand operand of an assignment时、pass it to或return it from a function by value时
- 智能指针p指向对象的reference count--的情形:assign a new value to p时、p被destroy时(such as when a local shared_ptr goes out of scope)
- Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages
- 栗子
auto r = make_shared<int>(42); // int to which r points has one user r = q; // assign to r, making it point to a different address // increase the use count for the object to which q points // reduce the use count of the object to which r had pointed // the object r had pointed to has no users; that object is automatically freed
- 另一个栗子
// factory returns a shared_ptr pointing to a dynamically allocated object shared_ptr<Foo> factory(T arg) { // process arg as appropriate return make_shared<Foo>(arg); } // 情形1 void use_factory(T arg) { shared_ptr<Foo> p = factory(arg); // use p } // p goes out of scope; the memory to which p points is automatically freed // 情形2 void use_factory(T arg) { shared_ptr<Foo> p = factory(arg); return p; // reference count is incremented when we return p } // p goes out of scope; the memory to which p points is not freed
- reference count
使用智能指针也可能出现不需要某对象但未释放其内存的特殊情况:把shared_ptr放在容器中时。所以,If you put shared_ptrs in a container, and you subsequently need to use some, but not all, of the elements, remember to erase the elements you no longer need.(erase之后reference count--,shared_ptr就会自动释放内存了)
把普通指针转化为智能指针后,不要再用普通指针访问智能指针管理的内存:下面这段代码中we passed a temporary shared_ptr to process. That temporary is destroyed when the expression in which the call appears finishes. 临时变量destroyed后,reference count 变为0,内存被释放,x变为悬空指针
// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr) {
// use ptr
} // ptr goes out of scope and is destroyed
int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer
process(x); // error: cannot convert int* to shared_ptr<int>
process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
int j = *x; // undefined: x is a dangling pointer!
unique_ptr
- 特点:“owns” the object to which it points;only one unique_ptr at a time can point to a given object
- 只有unique_ptr支持的操作
- 初始化:只能使用直接初始化
unique_ptr<double> p1; // unique_ptr that can point at a double
unique_ptr<int> p2(new int(42)); // p2 points to int with value 42
- 不支持复制和赋值,除非它即将被destroy(这时,the compiler does a special kind of “copy”,在第十三章会讲)
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // error: no copy for unique_ptr
unique_ptr<string> p3;
p3 = p2; // error: no assign for unique_ptr
unique_ptr<int> clone(int p) {
// ok: explicitly create a unique_ptr<int> from int*
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int (p));
// . . .
return ret; // ok: return a copy of a local object
}
- 支持transfer ownership
// transfers ownership from p1 (which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("Trex"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed
p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)
weak_ptr
- 特点:does not control the lifetime of the object to which it points,而是指向an object that is managed by a shared_ptr;最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况;不可使用* 和 ->访问对象
- 注意:binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr;Once the last shared_ptr pointing to the object goes away, the object itself will be deleted,即使there are weak_ptrs pointing to it
- 只有weak_ptr支持的操作
- 初始化:用shared_ptr或者另一个weak_ptr对象
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp weakly shares with p; use count in p is unchanged
- 访问元素:不能直接访问(因为可能被delete了),用lock:若元素仍存在,返回一个shared_ptr to the shared object(在lock()成功时会延长shared_ptr对象的生命周期,因为它递增了一个引用计数)
if (shared_ptr<int> np = wp.lock()) { // true if np is not null
// inside the if, np shares its object with p
}
- 使用场景:循环引用,这篇文章写得很详细
https://blog.csdn.net/albertsh/article/details/82286999
智能指针与exception
- 智能指针可以避免exception带来的内存泄露隐患:下面这段代码中,若an exception happens between the new and the delete, and is not caught inside f, then this memory can never be freed
void f() {
int *ip = new int(42); // dynamically allocate a new object
// code that throws an exception that is not caught inside f
delete ip; // free the memory before exiting
}
智能指针使用注意事项总结
- Don’t use the same built-in pointer value to initialize (or reset) more than one smart pointer
- Don’t delete the pointer returned from get()
- Don’t use get() to initialize or reset another smart pointer
- If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away
- If you use a smart pointer to manage a resource other than memory
allocated by new, remember to pass a deleter
使用智能指针的经典栗子
意图
- 之前使用的类,比如vector,allocate resources that exist only as long as the corresponding objects(比如When we copy a vector, the elements in the original vector and in the copy are separate from one another)
- 现在我们希望可以让两个类真正地共享一个对象,因为可能出现下面的情形,所以这个对象必须在堆内存中
Blob<string> b1; // empty Blob
{ // new scope
Blob<string> b2 = {"a", "an", "the"};
b1 = b2; // b1 and b2 share the same elements
} // b2 is destroyed, but the elements in b2 must not be destroyed,所以elements必须在堆中,否则其生存期会随着scope的结束而结束
类的定义
class StrBlob {
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back(const std::string &t) {data->push_back(t);}
void pop_back();
std::string& front();
std::string& back();
private:
std::shared_ptr<std::vector<std::string>> data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
构造函数
StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list<string> il): data(make_shared<vector<string>>(il)) { }
一些成员函数
void StrBlob::check(size_type i, const string &msg) const {
if (i >= data->size())
throw out_of_range(msg);
}
string& StrBlob::front()
{
// if the vector is empty, check will throw
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back() {
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back() {
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
使用这个类
StrBlob b1;
{
StrBlob b2 = {"a", "an", "the"};
b1 = b2;
b2.push_back("about");
}
Dynamic Arrays
注意
- dynamic array并不是array类型的,所以不支持begin\end\for这些
使用new
- 作用:allocates the requested number of objects and (assuming the allocation succeeds) returns a pointer to the first one
- 栗子:get_size()返回的值必须是integral type,但可以不是const
// call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; // pia points to the first of these ints
typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first one
- 初始化:If there are fewer initializers than elements, the remaining elements are value initialized;If there are more initializers than the given size, then the new expression fails and no storage is allocated 而且会抛出bad_array_new_length异常
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int[10](); // block of ten ints value initialized to 0
string *psa = new string[10]; // block of ten empty strings
string *psa2 = new string[10](); // block of ten empty strings
// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
// block of ten strings; the first four are initialized from the given initializers
// remaining elements are value initialized
string *psa3 = new string[10]{"a", "an", "the", string(3,'x')};
-
注意
- we cannot use auto to allocate an array
- classes that do not have default constructors cannot be dynamically allocated as an array
- 长度为0:Calling new[n] with n equal to 0 is legal even though we cannot create an array variable of size 0;下面代码的第二句:cp是一个valid, nonzero的指针,相当于长度为0的array的off-the-end pointer
char arr[0]; // error: cannot define a zero-length array char *cp = new char[0]; // ok: but cp can't be dereferenced
-
内存的释放
- 格式
delete [] pa; // pa must point to a dynamically allocated array or be null delete pa; // 若pa point to a dynamically allocated array,则结果undefined
- Elements in an array are destroyed in reverse order
-
使用unique_ptr管理动态数组
- 栗子
// up points to an array of ten uninitialized ints unique_ptr<int[]> up(new int[10]); up.release(); // automatically uses delete[] to destroy its pointer
- 指向数组时,不能用.和->
- 操作一览
-
使用shared_ptr管理动态数组
- shared_ptr并不直接支持动态数组,需要我们提供deleter
// to use a shared_ptr we must supply a deleter shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; }); sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
- 遍历数组
// shared_ptrs don't have subscript operator and don't support pointer arithmetic for (size_t i = 0; i != 10; ++i) *(sp.get() + i) = i; // use get to get a built-in pointer
使用allocator Class
- 优势:用new会将初始化和分配内存绑定在一起完成,而用allocator Class可以decouple memory allocation from object construction,从而
- 避免浪费:new expression allocates and initializes n strings但是we might not need n strings;elements are written twice: first when the elements are default initialized, and subsequently when we assign to them
string *const p = new string[n]; // construct n empty strings string s; string *q = p; // q points to the first string while (cin >> s && q != p + n) *q++ = s;
- 避免“使用new T[]时,T必须有默认构造函数”的限制
- 操作一览
- 栗子
allocator<string> alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings
auto q = p; // q will point to one past the last constructed element
alloc.construct(q++); // *q is the empty string
alloc.construct(q++, 10, 'c'); // *q is cccccccccc
alloc.construct(q++, "hi"); // *q is hi!
- 不要访问unconstructed memory
cout << *p << endl; // ok: uses the string output operator
cout << *q << endl; // disaster: q points to unconstructed memory!
- destroy与内存释放
- We may destroy only elements that are actually constructed
- destroy后,我们可以reuse the memory to hold other strings或者return the memory to the system
- deallocate中的指针不能为空指针,必须指向allocate分配的内存;deallocate中的数字必须be the same size as used in the call to allocate
while (q != p)
alloc.destroy(--q); // free the strings we actually allocated
alloc.deallocate(p, n);
-
construct objects in uninitialized memory的算法
- 算法一览
- 栗子
// allocate twice as many elements as vi holds auto p = alloc.allocate(vi.size() * 2); // construct elements starting at p as copies of elements in vi auto q = uninitialized_copy(vi.begin(), vi.end(), p); // initialize the remaining elements to 42 uninitialized_fill_n(q, vi.size(), 42);