网站怎么做聚合,thinkphp 网站模版,潍坊哪个网站公司做优化好,做网站的你选题的缘由是什么文章目录概念行为像值的类行为像指针的类概念引用计数动态内存实现计数器类的swap概念swap实现自赋值概念
行为像值的类和行为像指针的类这两种说法其实蛮拗口的#xff0c;这也算是 《CPrimer》 翻译的缺点之一吧。。。
其实两者的意思分别是#xff1a;
行为像值的类这也算是 《CPrimer》 翻译的缺点之一吧。。。
其实两者的意思分别是
行为像值的类 每个类的对象都有自己的实现行为像指针的类 所有类的对象共享类的资源类似于 shared_ptr 智能指针每有一个对象持有该资源则引用计数1每有一个对象释放该资源则引用计数-1引用计数为0时释放内存
本篇博客的内容跟 类 和 智能指针 两篇博客有关。不了解的同学可以先看看这两篇博客。 行为像值的类
对于类管理的资源每个对象都应该有一份自己的拷贝实现。如下面的 string类型的指针 使用拷贝构造函数 or 赋值运算符时每个对象拷贝的都是 指针成员ps 指向的 string 而非 ps本身 。换言之每个对象 都有一个ps 而不是 给ps加引用计数。
class A
{int i 0;string* ps;
public:A(const string s string()): ps(new string(s)), i(0) {}A(const A a): ps(new string(*a.ps)), i(a.i) {}A operator(const A);~A() { delete ps; }
};A A::operator(const A a)
{string* newps new string(*a.ps); // 将a.ps指向的值拷贝到局部临时对象newps中delete ps; // 销毁ps指向的内存避免旧内存泄漏ps newps; i a.i;return *this; // 返回此对象的引用
}为什么不能像下面这样实现赋值运算符呢
A A::operator(const A a)
{delete ps; // 销毁ps指向的内存避免内存泄漏ps new string(*(a.ps)); i a.i;return *this; // 返回此对象的引用
}这是因为如果 a 和 *this 是 同一个对象delete ps 会释放 *this 和 a 指向的 string。接下来当我们在 new表达式 中试图拷贝*(a.ps)时就会访问一个指向无效内存的指针即空悬指针其行为和结果是未定义的。
因此第一种实现方法可以确保销毁 *this 的现有成员操作是绝对安全的不会产生空悬指针。 行为像指针的类
概念
对于行为类似指针的类使用拷贝构造函数 or 赋值运算符时每个对象拷贝的都是 ps本身 而非 指针成员ps 指向的 string。换言之每有一个对象都是 给指向string的ps加引用计数。
因此析构函数不能粗暴地释放 ps 指向的 string 只有当最后一个指向 string 的 A类对象 销毁时才可以释放 string 。我们会发现这个特性很符合 shared_ptr 的功能因此我们可以使用 shared_ptr 来管理 像指针的类 中的资源。
但是有时我们需要程序员直接管理资源因此就要用到 引用计数reference count 了。 引用计数
工作方式
每个构造函数拷贝构造函数除外都要创建一个引用计数用来记录有多少对象与正在创建的对象共享状态。当我们创建一个对象时只有一个对象共享状态因此将计数器初始化为1。拷贝构造函数不分配新的计数器而是拷贝给定对象的数据成员包括计数器。拷贝构造函数递增共享的计数器指出给定对象的状态又被一个新用户所共享。析构函数递减计数器指出共享状态的用户少了一个。如果计数器变为0则析构函数释放状态。拷贝赋值运算符递增右侧运算对象的计数器递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0意味着它的共享状态没有用户了拷贝赋值运算符就必须销毁状态。
唯一的难题是确定在哪里存放引用计数。计数器不能直接作为 A对象 的成员。举个例子
A a1(cmy);
A a2(a1); // a2和a1指向相同的string
A a3(a2); // a1、a2、a3都指向相同的string如果计数器保存在每个对象中创建 a2 时可以递增 a1 的计数器并拷贝到 a2 中。可创建 a3 时诚然可以更新 a1 的计数器但怎么找到 a2 并将它的计数器更新呢
那么怎么处理计数器呢 动态内存实现计数器
class A
{int i 0;string *ps;size_t *use; // 记录有多少个对象共享*ps的成员
public:A(const string s string()): ps(new string(s)), i(0), use(new size_t(1)) {}A(const A a): ps(new string(*a.ps)), i(a.i), use(a.use) { *use; }A operator(const A);~A() {}
};
A::~A(){if(--*use 0){ // 引用计数变为0delete ps; // 释放string内存delete use; // 释放计数器内存}
}
A A::operator(const A a)
{*(a.use); // 之所以将计数器自增操作放这么前// 是为了防止自赋值时计数器自减导致ps、use直接被释放if(--(*use) 0){delete ps;delete use;}ps a.ps;i a.i;use a.use;return *this; // 返回此对象的引用
}类的swap
概念
我们在设计类的 swap 时虽然逻辑上是这样
A tmp a1;
a1 a2;
a2 tmp;但如果真的这样实现的话还需要创建一个新的对象 tmp效率是很低的造成了内存空间的浪费。因此我们实际上希望的是这样的逻辑实现
string *tmp a1.ps;
a1.ps a2.ps;
a2.ps tmp;创建一个 string类型 总比创建一个 A类对象 要省内存。具体实现
class A
{friend void swap(A, A);
};
inline void swap(A a1, A a2){using std::swap;swap(a1.ps, a2.ps);swap(a1.i, a2.i);
}swap实现自赋值
使用拷贝和交换的赋值运算符
A A::operator(A a){ // 传值使用拷贝构造函数通过实参右侧运算对象拷贝生成临时量aswap(*this, a); // a现在指向*this曾使用的内存return *this; // a的作用域结束被销毁delete了a中的ps
}上面重载的赋值运算符参数并不是一个引用也就是说 a 是右侧运算对象的一个副本。
在函数体中swap 交换了 a 和 *this 中的数据成员。*this 的 ps 指向右侧运算对象中 string 的一个副本*this 原来的 ps 存入 a 中。但函数体执行完a 作为局部变量被销毁delete了 a 中的 ps即 释放掉了左侧运算对象*this中原来的内存。
这个技术的有趣之处是它自动处理了自赋值情况且天然就是异常安全的。