动态内存与只能指针

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

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

动态内存与智能指针

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

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

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

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

make_shared函数

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

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

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

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

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

直接管理内存

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

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

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

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

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

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

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

动态分配的const对象

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

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

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

int *p1 = new int; // 如果分配失败, new抛出std::bad_alloc
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的, 因此不能将一个内置指针隐式转换为一个智能指针, 必须使用直接初始化形式来初始化一个智能指针

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

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

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

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

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

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

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

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

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

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

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

其他shared_ptr操作

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

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

智能指针和异常

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

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

智能指针陷阱:

  • 不要使用相同的内置指针初始化或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由于独占对象, 故不支持普通的拷贝和赋值操作.

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

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

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

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

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

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

unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone(int p)
{
unique_ptr<int> ret(new int (p));
// ...
return ret;
}

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

向unique_ptr传递删除器

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

weak_ptr

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

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

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

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

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

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

动态数组

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

new和数组

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

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

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

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

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

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

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

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

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

智能指针和动态数组:

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

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

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

指向数组的unique_ptr的操作:

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

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

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

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

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

allocator类

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

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

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

string *const p = new string[n]; // 构造n个空的string
string s;
string *q = p;
while(cin >> s && q != p + n)
*q++ = s;
const size_t size = q - p;
// 使用数组
delete[] p;

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

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

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

标准库allocator类及其算法:

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

    This note describes the wait events that monitor the performance of the log transport modes that wer ...

  2. mrh支付宝玩转福

    支付宝扫福 都会玩了 2017

  3. js数组去重的几种方法

    1.遍历数组法 最简单的去重方法, 实现思路:新建一新数组,遍历传入数组,值不在新数组就加入该新数组中:注意点:判断值是否在数组的方法“indexOf”是ECMAScript5 方法,IE8以下不支持 ...

  4. PHP:第一章——PHP中静态变量和常量

    <?php header("Content-Type:text/html;charset=utf-8"); /******************************** ...

  5. HDU 3226 背包

    转载自:http://www.cppblog.com/dango/archive/2010/08/26/124881.aspx 貌似是01背包的强化版.但是感觉这样写好理解些.就是01背包拓展了.

  6. Jquery中bind(), live(), on(), delegate()四种注册事件的优缺点,建议使用on()

    jquery中注册的事件,注册事件很容易理解偏差,叫法不一样.我第一反应就是如何添加事件,使用onclick之类的,暂时不讨论js注册事件的方法. 也看到园内前辈写过相关的帖子,但不是很详细,我找到了 ...

  7. mdadm 创建md 删除md步骤

    最近在使用mdadm创建和删除RAID设备.但是在创建和删除过程中会出现创建md0重启后变成md127,删除md127重启后又重新出现的状况.在网上搜索了一下,总结如下:   创建: 1.  mdad ...

  8. Python 文件路径

    # 文件路径: # 1. 相对路径: 相对于当前程序所在的文件夹, 如果在文件夹内, 随便找, 直接写名字 # 如果不在这个文件夹内, 可能需要出文件夹或者进文件夹 # 出文件夹 ../ # 进文件夹 ...

  9. 无法访问 MemoryStream 的内部缓冲区

    无法访问 MemoryStream 的内部缓冲区 在处理剪贴板数据时, ms.GetBuffer() 语句出现异常,代码如下: //检索当前位于系统剪贴板中的数据 IDataObject ido = ...

  10. IntelliJ IDEA 2017.01配置jdk和tomcat

    之前开发Web项目都是用myeclipse或者eclipse,最近想用IDEA这个编辑器去配置一个Web项目,因为是新手,加上对界面的操作不熟练,所以在配置的过程中遇到了一些难题.最后配置成功,并且可 ...