动态内存与只能指针

静态内存用来保存局部static对象, 类static数据成员以及定义在任何函数之外的变量. 栈内存用来保存定义在函数内的非static对象. 分配在静态或栈内存中的对象由编译器自动创建和销毁. 栈中的对象, 仅在其定义的程序块运行时才存在; static对象在使用之前分配, 在程序结束时销毁.

自由空间或堆用来存储动态分配的对象——即在程序运行时分配的对象. 动态对象的生存期由程序来控制, 也就是说, 当动态对象不在使用时, 我们的代码必须显示地销毁它们.

动态内存与智能指针

C++中动态内存的管理是通过new和delete运算符来完成.

新标准库提供了3种智能指针来管理动态对象, 它们可以自动释放所指的对象. 它们都定义在memory头文件中.

智能指针
shared_ptr
unique_ptr
weak_ptr
  1. shared_ptrunique_ptr都支持的操作
  2. shared_ptr<T> sp 空智能指针, 可以指向类型为T的对象
  3. unique_ptr<T> up
  4. p p用作一个条件判断, p指向一个对象, 则为true
  5. *p 解引用p, 获得它指向的对象
  6. p->mem 等价于(*p).mem
  7. p.get() 返回p中保存的指针, 要小心使用, 如果智能指针指针释放了其对象, 返回的指针所指的对象也就消失了
  8. swap(p, q) 交换pq中的指针
  9. p.swap(q)
  10. shared_ptr独有的操作:
  11. make_shared<T>(args) 是一个标准库函数, 返回一个shared_ptr, 指向一个动态分配的类型为T的对象. 使用args初始化此对象, args为空, 则执行值初始化.
  12. shared_ptr<T> p(q) pshared_ptr q的拷贝, 此操作会递增q中的计数器, q中的指针必须能转换成T*
  13. p = q 此操作会递增q的引用计数, 递减p的引用计数, p的引用计数变为0, 则将其管理的原内存释放
  14. p.unique() p.use_count()为1, 返回true, 否则返回false
  15. p.use_count() 返回与p共享对象的智能指针数量, 可能很慢, 主要用于调试.

每一个shared_ptr都有一个关联的计数器, 通常称其为引用计数. 拷贝一个shared_ptr, 引用计数会递增, 给shared_ptr赋予一个新值或被销毁, 计数器递减.

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数. make_shared用其参数来构造给定的对象.

  1. shared_ptr<string> p1; // 可以指向string
  2. shared_ptr<list<string>> p2; // 可以指向string的list
  3. shared_ptr<int> p3 = make_shared<int>(42); // 指向一个值为42的int的shared_ptr
  4. shared_ptr<string> p4 = make_shared<string>(3, '9'); // 指向值为"999"的string
  5. shared_ptr<int> p5 = make_shared<int>(); // 指向一个值初始化的int, 值为0
  6. auto p6 = make_shared<vector<string>>();
  7. auto p7(p6); // p6, p7指向相同的对象.
  1. class MyClass
  2. {
  3. public:
  4. typedef std::shared_ptr<MyClass> sptr;
  5. static sptr make()
  6. {
  7. return sptr(new MyClass());
  8. }
  9. static sptr make2(int x, int y)
  10. {
  11. return make_shared<MyClass>(x, y);
  12. }
  13. MyClass(int x1 = 0, int y1 = 0): x(x1), y(y1) { }
  14. ~MyClass(){ }
  15. private:
  16. int x;
  17. int y;
  18. };
  19. int main()
  20. {
  21. MyClass::sptr sp_mc = MyClass::make(); // 返回智能指针管理的动态类对象
  22. sp_mc = MyClass::make2(10, 24); // sp_mc原来指向的对象内存会释放, 然后sp_mc指向一个新对象.
  23. shared_ptr<MyClass> sp_mc2 = make_shared<MyClass>(14, 20);
  24. return 0;
  25. }

如果将shared_ptr存放在一个容器中, 而后不再需要全部元素, 而只使用其中一部分, 要用erase删除不再需要的那些元素.

程序使用动态内存的三个原因:

  • 程序不知道自己需要多少资源, 如容器类是出于该原因而使用动态内存的典型例子.
  • 程序不知道所需对象的准确类型.
  • 程序需要在多个对象间共享数据.

直接管理内存

new与delete, new[]和delete[]成对使用.

自由空间分配的内存是无名的, 因此new无法为其分配的对象命名, 而是返回一个指向该对象的指针.

默认情况下, 动态分配的对象是默认初始化的, 这意味着内置类型或组合类型的对象的值将是未定义的, 而类类型对象将用默认构造函数进行初始化.

也可以使用直接初始化方式, 可以使用传统的构造方式(使用圆括号), 在新标准下, 也可以使用列表初始化(使用花括号), 还可以进行值初始化, 只需在类型名后面跟一对空括号即可. 对于定义了自己的构造函数的类类型来说, 值初始化和默认初始化是一样的, 都会调用默认构造函数来初始化.

  1. int *pi = new int(1024); // 直接初始化, 用传统的构造方式(使用圆括号)
  2. int *ps = new string(10, '9'); // 直接初始化, 用传统的构造方式
  3. vector<int> *pv = new vector<int>{0,1,2,3} // 直接初始化, 用初始化列表的方式
  4. string *ps1 = new string; // 默认初始化, 空string, 调用默认构造函数
  5. string *ps2 = new string(); // 值初始化, 初始化为空string
  6. int *pi1 = new int; // 默认初始化, *pi1的值未定义
  7. int *pi2 = new int(); // 值初始化为0

如果我们提供了一个括号包围的初始化器, 就可以使用auto来从此初始化器来推断我们想要分配的对象的类型. 由于编译器需要要初始化器的类型来推断要分配的类型, 只有当括号中仅有单一初始化器时才可以使用auto.

  1. auto p1 = new auto(obj); // p1指向一个与obj类型相同的对象, 该对象用obj来初始化
  2. auto p2 = new auto{a, b, c}; // 错误, 括号中只能有单个初始化器.

动态分配的const对象

一个动态分配的const对象必须进行初始化, 由于分配的对象是const的, new返回的指针是一个指向const的指针.

默认情况下, new如果不能分配所要求的内存空间, 它会抛出一个类型为bad_alloc的异常, 而C中的malloc则会返回一个NULL指针.

定位new: 定位new表达式允许我们向new传递额外参数.

  1. int *p1 = new int; // 如果分配失败, new抛出std::bad_alloc
  2. int *p2 = new (nothrow) int; // 如果分配失败, new返回一个空指针

传递给new一个有标准库定义的名为nothrow的对象, 意思是告诉它不能抛出异常, 如果这种形式的new不能分配所需的内存, 就会返回一个空指针, 而不会抛出异常. bad_alloc和nothrow都定义在new头文件中.

释放动态内存

delete接受一个指针, 指向我们想要释放的对象. delete表达式执行两个动作: 销毁给定的指针所指向的对象; 释放对应的内存.

传递给delete的指针必须指向动态分配的内存, 或者是一个空指针. 释放一块并非new分配的内存, 或者将相同的指针释放多次, 其行为是未定义的. 释放一个空指针总是没有错误的, 它们通常什么也不做.

通常情况下, 编译器不能分辨一个指针指向的是静态还是动态分配的对象, 类似的, 编译器也不能分辨一个指针所指向的内存是否已经被释放. 传递给delete一个非new分配的内存指针, 尽管大多数编译器可以编译通过, 但它们是错误的, 其行为将是未定义的.

虽然一个const对象的值不能被改变, 但它本身是可以被销毁的.

有内置指针(而非智能指针)管理的动态内存在被显示释放之前一直都会存在.

使用new和delete管理动态内存存在3个常见的问题:

  • 忘记delete内存.
  • 使用已经释放掉的内存.
  • 同一块内存释放两次.

delete之后记得重置指针值. 在delete之后, 指针就变成了人们所说的空悬指针, 将nullptr赋予指针, 这样就清楚地指针指针不再指向任何对象. 但即使是这样, 这也只是提供了有限的保护, 因为还有可能有多个指针指向相同的内存. delete之后重置指针的办法只对这个指针有效, 对其他仍指向这块以释放内存的指针是没有作用的.

shared_ptr和new结合使用

如果不初始化一个智能指针, 它就会被初始化为一个空指针. 可以用new返回的指针来初始化智能指针. 接受指针参数的智能指针构造函数是explicit的, 因此不能将一个内置指针隐式转换为一个智能指针, 必须使用直接初始化形式来初始化一个智能指针

  1. shared_ptr<double> p1;
  2. shared_ptr<int> p1 = new int(1024); // 错误, 必须使用直接初始化形式
  3. shared_ptr<int> p2(new int(1024)); // 正确, 使用直接初始化形式.
  4. shared_ptr<int> clone(int p)
  5. {
  6. //return new int(p); //错误, 返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针.
  7. return shared_ptr<int>(new int(p)); //正确, 显示地用int *创建shared_ptr<int>
  8. }

默认情况下, 一个用来初始化智能指针的普通指针必须指向动态内存, 因为智能指针默认使用delete来释放它所关联的对象. 也可以将智能指针绑定到一个指向其他类型的资源的指针上, 但是为了这样做, 必须提供自己的操作来代替delete.

  1. shared_ptr<T> p(q); // p管理内置指针q所指向的对象, q必须指向new分配的内存且能够转换为T*类型
  2. shared_ptr<T> p(u); // p从unique_ptr u那里接管对象的所有权, 将u置为空.
  3. shared_ptr<T> p(q, d); // p接管了内置指针q所指向的对象的所有权. q必须能够转换为T*类型, p将使用可调用对象d来代替delete.
  4. shared_ptr<T> p(p2, d); // p是shared_ptr p2的拷贝, 唯一的区别是p将用可调用对象d来代替delete
  5. p.reset(); // 若p是唯一指向其对象的shared_ptr, reset会释放此对象. 若传递了可选的参数内置指针q, 会令p指向q, 否则会将p置空. 若还传递了可调用对象d, 则会使用d而不是delete来释放对象.
  6. p.reset(q);
  7. p.reset(q, d);

不要混合使用普通指针和智能指针

shared_ptr可以协调对象的析构, 但这仅限于自身的拷贝(也是shared_ptr)之间, 这也是为什么推荐使用make_shared而不是new的原因.

当将一个shared_ptr绑定到一个普通指针时, 我们就将内存的管理责任交给了这个shared_ptr, 一旦这样做了, 就不应该再使用内置指针来访问shared_ptr所指向的内存了.

  1. void process(shared_ptr<int> ptr)
  2. {
  3. //do something
  4. } // ptr离开作用域, 被销毁
  5. shared_ptr<int> p(new int(42)); // 引用计数为1
  6. process(p); // 拷贝p会递增其引用计数, 在process中其引用计数为2
  7. int i = *p; // 正确, 引用计数值为1
  8. int *x(new int(1024)); // 危险: x是一个普通指针, 而非智能指针
  9. //process(x); // 错误, 不能隐式转换
  10. process(shared_ptr<int>(x)); // 创建一个临时的shared_ptr对象传递给process, process结束后该临时对象会被销毁, 此时其引用计数为0, x指向的内存会被释放.
  11. int j = *x; // 未定义的行为, x是一个空悬指针.

也不要使用get初始化另一个智能指针或为智能指针赋值:

智能指针的get函数返回一个智能指针, 指向智能指针管理的对象, 此函数是为了这样一种情况而设定: 我们需要向不能使用智能指针的代码传递一个内置指针. 使用get返回指针的代码不能delete此指针.

  1. shared_ptr<int> p(new int(42));
  2. int *q = p.get(); //正确, 但是注意不要让它管理的指针被释放
  3. {
  4. // 新的程序块
  5. shared_ptr<int>(q) // 未定义行为, 两个独立的shared_ptr指向相同的内存
  6. } // 程序块结束, q被销毁, 它指向的内存被释放
  7. int foo = *p; // 未定义: p指向的内存已经被释放了

只有在确定代码不会delete指针的情况下才能使用get, 特别是永远不要使用get初始化另一个智能指针或者为另一个智能指针赋值.

其他shared_ptr操作

reset经常和unique一起使用, 来控制多个shared_ptr共享的对象.

  1. if(!p.unique) // 如果不是唯一用户就做一份拷贝
  2. p.reset(new string(*p));
  3. *p += newVal; // 此时自己是唯一用户了

智能指针和异常

确保在异常发生后资源能被正确的释放.

  1. struct destination;
  2. struct connection;
  3. connection connect(destination*);
  4. void disconnect(connection);
  5. void f(destination &d/*其他参数*/)
  6. {
  7. // 获得一个连接; 记住使用完后要关闭它
  8. connection c = connect(&d);
  9. // 使用连接
  10. // 如果在f退出前忘记调用disconnect或是由于异常而无法调用disconnect, 就无法关闭c了.
  11. }
  12. // 使用shared_ptr来保证connection被正确关闭, 这是一种有效的方法.
  13. void end_connection(connection *p) // 提供自己的删除器
  14. {
  15. disconnect(*p);
  16. }
  17. void f(destination &d/*其他参数*/)
  18. {
  19. connection c = connect(&d);
  20. shared_ptr<connection> p(&c, end_connection); // 默认会使用delete来释放资源, 但是可以提供自己的删除器
  21. //使用连接
  22. //当f退出时(即使是由于异常而退出), connection会被正确关闭
  23. }

智能指针陷阱:

  • 不要使用相同的内置指针初始化或reset多个智能指针. 因为这会导致个智能指针独立创建, 各自的引用计数器是独立的, 即不知道还有其他智能指针指向同一块内存.
  • 不要delete有get()返回的指针.
  • 不要使用get()初始化或reset另一个智能指针.
  • 如果使用get()返回的指针, 谨记当最后一个对应的智能指针销毁后, 你的指针就变为无效了.
  • 如果你使用智能指针管理的资源不是new分配的内存, 记住传递给它一个删除器.

unique_ptr

与shared_ptr不同, 任一时刻, 只能有一个unique_ptr指向一个给定的对象, 也没有类似make_shared的标准库函数返回一个unique_ptr. 当我们定义一个unique_ptr时, 需要将其绑定到一个new返回的指针上. 类似与shared_ptr, 初始化unique_ptr时也必须采用直接初始化方式. unique_ptr由于独占对象, 故不支持普通的拷贝和赋值操作.

  1. unique_ptr<T> u1 unique_ptr, u1会使用delete来释放其指针, u2使用一个类型为D的可调用对象来释放指针
  2. unique_ptr<T, D> u2
  3. unique_ptr<T, D> u(d) unique_ptr, 使用类型为D的可调用对象d代替delete.
  4. u = nullptr 释放u指向的对象, u置空
  5. u.release() u放弃对指针的控制权, 返回指针, 并将u置空
  6. u.reset() 释放u所指的对象, 如果提供了内置指针q, u指向这个对象, 否则将u置空.
  7. u.reset(q)
  8. u.reset(nullptr)

虽然无法拷贝或赋值unique_ptr, 但可以通过调用release或reset将指针的所有权从一个unique_ptr转移给另一个unique.

  1. unique_ptr<string> p2(p1.release()); // p1放弃对指针的控制权, 返回指针, 并将p1置空
  2. unique_ptr<string> p3(new string("Trex"));
  3. p2.reset(p3.release()); // 释放p2, p3置空, p2指向p3曾将指向的内存.
  4. p2.release(); // 错误, p2不会释放内存, 而且丢失了指针.
  5. auto p = p2.release(); // 正确, 但是必须记得delete(p)

release会切断unique_ptr与它原来管理的对象之间的联系. reset则会释放其所指的资源.

不能拷贝unique_ptr的规则有一个例外

我们可以拷贝或赋值一个将要被销毁的unique_ptr, 最常见的例子是从函数返回一个unique_ptr.

  1. unique_ptr<int> clone(int p)
  2. {
  3. return unique_ptr<int>(new int(p));
  4. }
  5. unique_ptr<int> clone(int p)
  6. {
  7. unique_ptr<int> ret(new int (p));
  8. // ...
  9. return ret;
  10. }

编译器知道要返回的对象将要被销毁, 在此情况下, 编译器执行一种特殊的拷贝.

向unique_ptr传递删除器

  1. // 其他函数同shared_ptr
  2. void f(destination &d/*其他参数*/)
  3. {
  4. connection c = connect(&d); // 打开连接
  5. unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
  6. //使用连接
  7. //当f退出时(即使是由于异常而退出), connection会被正确关闭
  8. }

weak_ptr

weak_ptr是一种不控制所指对象生存期的智能指针, 它指向一个由shared_ptr管理的对象, 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数. 一旦最后一个指向对象的shared_ptr被销毁, 对象就会被释放, 即使有weak_ptr指向对象, 对象还是会被释放.

  1. weak_ptr<T> w weak_ptr
  2. weak_ptr<T> w(sp) shared_ptr指向相同对象的weak_ptr. T必须能够转换为sp指向的类型
  3. w = p p可以是一个weak_ptr或一个shared_ptr, 赋值后wp共享对象
  4. w.reset() w置空
  5. w.use_count() w共享对象的shared_ptr的数量
  6. w.expired() w.use_count()为0, 则返回true, 否则返回false.
  7. w.lock() 如果expiredtrue, 返回一个空shared_ptr, 否则返回一个指向w的对象的shared_ptr.

当创建一个weak_ptr时, 需要用一个shared_ptr来初始化它.

  1. auto p = make_shared<int>(42);
  2. weak_ptr<int> wp(p); // wp弱共享p, p的引用计数未改变

由于对象可能不存在, 我们不能直接用weak_ptr直接访问对象, 而必须调用lock. 此函数检查weak_ptr指向的对象是否仍存在. 如果存在, lock返回一个指向共享对象的shared_ptr.

  1. if(shared_ptr<int> np = wp.lock()) { // 先执行赋值, 再判断np是否为空
  2. // 在if语句中, np与p共享对象
  3. }

动态数组

大多数应用应该使用标准库容器而不是动态分配的数组. 使用容器更为简单, 更不容易出现内存管理错误并且可能有更好的性能.

new和数组

  1. int *p = new int[get_size()]; // p指向第一个int的指针, 大小必须是整型, 但不必是常量

动态数组不是数组类型, 因此不能对动态数组调用begin和end.

默认情况下, new分配的对象都是默认初始化的, 可以对数组进行值初始化, 大小后面跟上一对空括号即可.

  1. int *pia = new int[10]; // 10和未初始化的int
  2. int *pia2 = new int[10](); // 10个值初始化的int
  3. string *psa = new string[10]; // 10个空string
  4. string *psa2 = new string[10](); // 10个空string
  5. int *pia3 = new int[10]{1, 2, 3}; // 前3个用列表中对应的初始化器初始化, 剩余的进行值初始化

新标准中还可以提供一个元素初始化器的花括号列表. 如果初始化器数目小于元素数目, 剩余元素将进行值初始化, 如果初始化器数目大于元素数目, 则new表达式失败, 不会分配任何内存.

虽然可以用空括号对数组中的元素进行值初始化, 但不能在括号中给出初始化器, 这意味着不能用auto分配数组. new用于非数组时可以在括号内提供初始化器, 用于数组时则不行.

可以动态分配一个空数组, new会返回一个合法的非空指针, 它保证与new返回的其他任何指针都不相同, 此指针就像尾后指针一样, 不能解引用.

  1. char arr[0]; // 错误, 不能定义长度为0的数组
  2. char *cp = new char[0]; // 正确, 但是cp不能解引用

释放动态数组必须要使用delete[], 忽略方括号其行为是未定义的.

智能指针和动态数组:

标准库提供了一个可以管理new分配的数组的unique_ptr版本. 为了用一个unique_ptr管理动态数组, 必须在对象类型后面跟上一对空方括号.

  1. unique_ptr<int[]> up(new int[10]{1, 2, 3});
  2. for(size_t i = 0; i != 10; ++i)
  3. up[i] = i; // 可以用下标访问数组中的每一个元素
  4. up.reset(); // 调用delete[]释放动态内存

指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符), 因为unique_ptr指向的是一个数组而不是单个对象. 可以使用下标运算符来访问数组中的元素.

指向数组的unique_ptr的操作:

  1. unique_ptr<T[]> u;
  2. unique_ptr<T[]> u(p);
  3. u[i]; 返回u拥有的数组中位置i处的对象.

shared_ptr不直接支持管理动态数组, 如果希望用shared_ptr管理一个动态数组, 必须提供自己定义的删除器.

  1. // 传递一个lambda作为删除器, 它使用delete[]来释放数组.
  2. shared_ptr<int> sp(new int[10], [](int *p){ delete[] p; });
  3. // shared_ptr没有定义[]运算符, 并且不支持指针的算数运算
  4. for(size_t i = 0; i != 10; ++i)
  5. *(sp.get() + i) = i; // 使用get()获取一个内置指针
  6. sp.reset(); // 使用我们提供的lambda释放数组, 它使用delete[]

如果未提供删除器, 则代码是未定义的, 因为shared_ptr默认使用delete而不是delete[]来销毁对象.

shared_ptr未定义下标运算符, 而且智能指针类型不支持指针算术运算. 为访问数组中的元素, 必须用get()获取一个内置指针, 然后用它来访问数组元素.

allocator类

new将内存分配和对象构造结合在了一起. delete将对象析构和内存释放组合在一起. 这些使得new有一些灵活上的局限性.

当分配一大块内存时, 通常计划在这块内存上按需构造对象, 此时我们希望将内存分配和对象构造分离. 着意味着只在真正需要时才真正执行对象创建操作.

一般情况下, 将内存分配和对象构造组合在一起可能导致不必要的浪费.

  1. string *const p = new string[n]; // 构造n个空的string
  2. string s;
  3. string *q = p;
  4. while(cin >> s && q != p + n)
  5. *q++ = s;
  6. const size_t size = q - p;
  7. // 使用数组
  8. delete[] p;

new初始化并分配了n个string. 但是可能不需要n个string. 这样可能就创建了一些永远用不到的对象. 对于确实需要使用的对象, 初始化之后立即赋予新值, 每个使用到的元素都被赋值两次. 更重要的是, 那些没有默认构造函数的类就不能动态分配数组了.

allocator类定义在头文件memory中, 它提供一种类型感知的内存分配方法, 它分配的内存时原始的, 未构造的.

allocator是一个模板, 当一个allocator对象分配内存时, 它会根据给定的对象类型来确定恰当的内存大小和对齐位置.

标准库allocator类及其算法:

  1. allocator<T> a 定义一个名为aallocator对象, 它可以为类型为T的对象分配内存
  2. a.allocate(n) 分配一段原始的, 未构造的内存, 保存n个类型为T的对象
  3. a.deallocate(p, n) 释放从T*指针p中地址开始的内存, 这块内存保存了n个类型为T的对象; p必须是一个先前由allocate返回的指针, n必须是p创建时所要求的大小. 在调用deallocate之前, 用户必须对内存中的每个对象调用destory()
  4. a.construct(p, args) p是一个T*的指针, 指向一块原始内存, args被传递给类型为T的构造函数, p所指的内存中构造一个对象.
  5. a.destroy(p) pT*类型的指针, 此算法对p所指的对象执行析构函数.
  1. allocator<string> alloc; // 可以分配string的allocator对象
  2. auto const p = alloc.allocate(n); // 分配n个未初始化的string
  3. auto q = p; // q在后续操作中将指向最后构造的元素之后的位置.
  4. alloc.construct(q++); // 空串
  5. alloc.construct(q++, 3, 'c'); // ccc
  6. alloc.construct(q++, "hi"); // hi
  7. cout << *p << endl; // 正确, p指向第一个已经构造过的对象
  8. cout << *q << endl; // 错误, q指向未构造的内存
  9. while(q != p)
  10. alloc.destory(--q); // 释放真正构造的string, 没有释放内存
  11. alloc.deallocate(p, n); // 释放内存

allocator将内存分配释放和对象构造析构分成了两个阶段: 内存配置由allocate负责, 内存释放由deallocate负责. 对象构造操作由construct负责, 对象析构由destroy负责.

拷贝和填充未初始化内存的算法

这些函数在给定目的位置创建元素, 而不是由系统分配内存给它们.

uninitialized_copy(b, e, b2) 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中

uninitialized_copy_n(b, n, b2) 从迭代器b指向的元素开始, 拷贝n个元素到b2开始的内存中

uninitialized_fill(b, e, t) 在迭代器b和e指定的原是内存范围中创建对象, 对象的值均为t的拷贝

uninitialized_fill_n(b, n, t) 从迭代器b指向的内存地址开始创建n个对象, b必须指向足够大的未构造的原始内存, 能够容纳给定数量的对象.

以上函数均返回一个指向最后一个构造的元素之后的位置的迭代器.

[C++ Primer] : 第12章: 动态内存的更多相关文章

  1. 【c++ Prime 学习笔记】第12章 动态内存

    对象的生存期: 全局对象:程序启动时创建,程序结束时销毁 局部static对象:第一次使用前创建,程序结束时销毁 局部自动对象:定义时创建,离开定义所在程序块时销毁 动态对象:生存期由程序控制,在显式 ...

  2. C++ Primer 5th 第12章 动态内存

    练习12.1:在此代码的结尾,b1 和 b2 各包含多少个元素? StrBlob b1; { StrBlob b2 = {"a", "an", "th ...

  3. 《C++ Primer》笔记 第12章 动态内存

    shared_ptr和unique_ptr都支持的操作 解释 shared_ptr sp或unique_ptr up 空智能指针,可以指向类型为T的对象 p 将p用作一个条件判断,若p指向一个对象,则 ...

  4. C++ Primer : 第十三章 : 动态内存管理类

    /* StrVec.h */ #ifndef _STRVEC_H_ #define _STRVEC_H_ #include <memory> #include <string> ...

  5. 深入理解java虚拟机-第12章Java内存模型与线程

    第12章 Java内存模型与线程 Java内存模型  主内存与工作内存: java内存模型规定了所有的变量都在主内存中,每条线程还有自己的工作内存. 工作内存中保存了该线程使用的主内存副本拷贝,线程对 ...

  6. C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常

    shared_ptr和new结合使用 一个shared_ptr默认初始化为一个空指针.我们也可以使用new返回的指针来初始化一个shared_ptr: shared_ptr<double> ...

  7. C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)

    C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存. 运算符new和delete 使用new动态分配和初始化对象 在自由空间分配的内存是无名的 ...

  8. C++ Primer : 第十二章 : 动态内存之shared_ptr类实例:StrBlob类

    StrBlob是一个管理string的类,借助标准库容器vector,以及动态内存管理类shared_ptr,我们将vector保存在动态内存里,这样就能在多个对象之间共享内存. 定义StrBlob类 ...

  9. C++ Primer : 第十二章 : 动态内存之shared_ptr类

    在C++中,动态内存是的管理是通过一对运算符来完成的:new  ,在动态内存中为对象分配空间并返回一个指向该对象的指针,delete接受一个动态对象的指针,销毁该对象,并释放该对象关联的内存. 动态内 ...

随机推荐

  1. dubbo的超时重试

    dubbo的超时分为服务端超时 SERVER_TIMEOUT 和客户端超时 CLIENT_TIMEOUT.本文讨论服务端超时的情形: 超时:consumer发送调用请求后,等待服务端的响应,若超过ti ...

  2. MySQL使用全文索引(fulltext index)---高性能

    转载地址:https://blog.csdn.net/u011734144/article/details/52817766/ 1.创建全文索引(FullText index) 旧版的MySQL的全文 ...

  3. 使用scrapy-redis构建简单的分布式爬虫

    前言 scrapy是python界出名的一个爬虫框架.Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中. 虽然scr ...

  4. [转载]python的常用代码模板

    URL:http://blog.csdn.net/xingjiarong/article/details/50651235

  5. JVM --- OutOfMemoryError异常

    在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有可能发生OutOfMemoryError(OOM)异常. 1.Java堆溢出 Java堆用于存储对象实例,只要不断地创 ...

  6. pdo 数据库链接

    在PHP中,我们还可以使用一种更为简单直接的数据库连接方案——PDO持久化连接. 关于PDO本身,这里就不再多作介绍了,大家可以参考之前的文章<使用PDO连接多种数据库>以及PHP官方网站 ...

  7. radio属性添加

    经常会遇到js控制radio选中和切换的问题 之前一直使用的是checked属性来完成的 但是现在发现这个属性有个大问题 今天就是用js给选中radio的赋值,使用的$().attr("ch ...

  8. PTA 大炮打蚊子   (15分)

    现在,我们用大炮来打蚊子:蚊子分布在一个M×NM\times NM×N格的二维平面上,每只蚊子占据一格.向该平面的任意位置发射炮弹,炮弹的杀伤范围如下示意: O OXO O 其中,X为炮弹落点中心,O ...

  9. 接口测试之postman-简单使用

    Postman功能(https://www.getpostman.com/features) 主要用于模拟网络请求包 快速创建请求 回放.管理请求 快速设置网络代理 安装 下载地址:https://w ...

  10. FZU 1202

    http://acm.fzu.edu.cn/problem.php?pid=1202 二分图最大匹配,问哪些边是必要的,O(n^3)的方法 删边的时候把连接关系也要删掉,如果在此基础上无法找到增广路, ...