因为今天突然看到一篇帖子在讨论C++的赋值构造,也就是 operator = 的重载, 里面说到了一个“自赋值”的问题,以前自己也看过Thinking In C++中有介绍到这个问题,于是又翻起书来,重温了一遍,发现,里面介绍到了一个很重要的技术,也就是标题所说的,Reference Counting(引用计数)的技术
那么,什么是“引用计数”呢?它又有什么用呢?
在我们构建对象的时候,例如,通过拷贝构造函数和operator=对一个类的对象的引用进行了一次新的构建,也就是产生了一个新的对象,有可能,一个项目中,会有很多次调用拷贝构造的情况,而且,有时候,很多对象的构建,只是为了读,也就是拿来用,而不会对其进行写入,那么,这个时候,为了节省内存的开销和效率的提高,我们就可以使用一种技术——引用计数。
Thinking In C++一书中是这么说的:
You give intelligence to the object that’s being pointed to so it knows how many objects are pointing to it. Then copy-construction or assignment means attaching another pointer to an existing object and incrementing the reference count. Destruction means reducing the reference count and destroying the object if the reference count goes to zero.
应该能看懂吧,我就不详细翻译了,因为怕让人产生歧义,还是原文简单易懂。这里只是简单说明一下:你应该让这个对象知道自己被引用了多少次,当执行一次拷贝或者赋值的时候,就应该让这个对象的引用次数(ref count)加一,析构函数应该让ref count减一,然后,在ref count = 0的时候,释放这个对象的内存。
那么,这个时候,很有可能会产生一个问题:当我有时需要对我这个类的对象进行write,也就是,改变其中的某个属性,那么,不是会造成与这个对象共用同一块内存的对象的属性也改变吗?这个时候,就需要用到另一个技术:copy on write(有人翻译成“写拷贝”,也就是说,当我需要对这个对象进行“写”操作,即,如上所说,改变其某个属性的时候,就应该copy一下这个对象的内容去构建一个新的对象,而不是简单的在原来这个对象上进行修改)。
那么,应该怎么来控制这个写操作呢?其实很简单,只需要在这个类中对其对象的属性有修改的成员函数中,在修改属性之前,判断一下,这个对象的存储单元当前没有被其他的对象引用,也就是ref count = 1,此时,才可以对当前对象进行改变;如果,ref count > 1,那么,就需要在这个函数最开始部分先开辟一块新的内存来copy一个与当前对象属性相同的对象。
说了这么一大通,到底应该怎么做呢?我们直接来看代码:同样摘自Thinking In c++ ,
可能有点长,不过,为了搞清楚,也不能省事,其中加入了自己的注释,便于理解
class Dog {
string nm;
int refcount;
Dog(const string& name)
: nm(name), refcount(1) {
cout << "Creating Dog: " << *this << endl;
}
// Prevent assignment:
Dog& operator=(const Dog& rv);
public:
// Dogs can only be created on the heap:
static Dog* make(const string& name) { //注意,此处是静态方法make来产生新的Dog对象指针
return new Dog(name);
}
Dog(const Dog& d) //copy constructor of class Dog
: nm(d.nm + " copy"), refcount(1) {
cout << "Dog copy-constructor: "
<< *this << endl;
}
~Dog() {
cout << "Deleting Dog: " << *this << endl;
}
void attach() { //当前有新的对象与当前对象的内存块相关联
++refcount;
cout << "Attached Dog: " << *this << endl;
}
void detach() { //判断当前ref count是否等于0,来决定是否要释放内存
cout << "Detaching Dog: " << *this << endl;
// Destroy object if no one is using it:
if(--refcount == 0) delete this;
}
// Conditionally copy this Dog.
// Call before modifying the Dog, assign
// resulting pointer to your Dog*.
Dog* unalias() {
cout << "Unaliasing Dog: " << *this << endl; //判断当前Dog对象是否需要copy on write
// Don't duplicate if not aliased:
if(refcount == 1) return this;
--refcount;
// Use copy-constructor to duplicate:
return new Dog(*this);
}
void rename(const string& newName) { //这个就是用来对Dog对象进行"写"操作的函数
nm = newName;
cout << "Dog renamed to: " << *this << endl;
}
friend ostream& //为了便于追踪,重写的operator <<
operator<<(ostream& os, const Dog& d) {
return os << "[" << d.nm << "], rc = "
<< d.refcount;
}
};
class DogHouse {
Dog* p;
string houseName;
public:
DogHouse(Dog* dog, const string& house)
: p(dog), houseName(house) {
cout << "Created DogHouse: "<< *this << endl;
}
DogHouse(const DogHouse& dh)
: p(dh.p),
houseName("copy-constructed " +
dh.houseName) {
p->attach();
cout << "DogHouse copy-constructor: "
<< *this << endl;
}
DogHouse& operator=(const DogHouse& dh) {
// Check for self-assignment:
if(&dh != this) {
houseName = dh.houseName + " assigned";
p->detach(); // Clean up what you're using first:
p = dh.p; // Like copy-constructor
p->attach();
}
cout << "DogHouse operator= : "
<< *this << endl;
return *this;
}
// Decrement refcount, conditionally destroy
~DogHouse() {
cout << "DogHouse destructor: "
<< *this << endl;
p->detach();
}
void renameHouse(const string& newName) {
houseName = newName;
}
//DogHouse通过调用Dog::unalias()来判断是否需要copy on write
void unalias() { p = p->unalias(); }
// Copy-on-write. Anytime you modify the
// contents of the pointer you must
// first unalias it:
void renameDog(const string& newName) {
unalias(); //在对Doghouse成员Dog的属性进行改变的时候,必须要先调用unalias判断,是否需要copy on write
p->rename(newName);
}
// ... or when you allow someone else access:
Dog* getDog() { //调用这个函数,也有可能在外界改变对象属性,所以,也应该先调用unalias
unalias();
return p;
}
friend ostream&
operator<<(ostream& os, const DogHouse& dh) {
return os << "[" << dh.houseName
<< "] contains " << *dh.p;
}
};
int main() {
DogHouse
fidos(Dog::make("Fido"), "FidoHouse"),
spots(Dog::make("Spot"), "SpotHouse");
cout << "Entering copy-construction" << endl;
DogHouse bobs(fidos);
cout << "After copy-constructing bobs" << endl;
cout << "fidos:" << fidos << endl;
cout << "spots:" << spots << endl;
cout << "bobs:" << bobs << endl;
cout << "Entering spots = fidos" << endl;
spots = fidos;
cout << "After spots = fidos" << endl;
cout << "spots:" << spots << endl;
cout << "Entering self-assignment" << endl;
bobs = bobs;
cout << "After self-assignment" << endl;
cout << "bobs:" << bobs << endl;
cout << "Entering rename(/"Bob/")" << endl;
//bobs.getDog()->rename("Bob"); //如果把这行注释了,那么,运行的结果,就是下面的结果
cout << "After rename(/"Bob/")" << endl;
} ///:~
程序运行的结果如下:
Creating Dog: [Fido], rc = 1
Created DogHouse: [FidoHouse]
contains [Fido], rc = 1
Creating Dog: [Spot], rc = 1
Created DogHouse: [SpotHouse]
contains [Spot], rc = 1
Entering copy-construction
Attached Dog: [Fido], rc = 2
DogHouse copy-constructor:
[copy-constructed FidoHouse]
contains [Fido], rc = 2
After copy-constructing bobs
fidos:[FidoHouse] contains [Fido], rc = 2
spots:[SpotHouse] contains [Spot], rc = 1
bobs:[copy-constructed FidoHouse]
contains [Fido], rc = 2
Entering spots = fidos
Detaching Dog: [Spot], rc = 1
Deleting Dog: [Spot], rc = 0
Attached Dog: [Fido], rc = 3
DogHouse operator= : [FidoHouse assigned]
contains [Fido], rc = 3
After spots = fidos
spots:[FidoHouse assigned] contains [Fido],rc = 3
Entering self-assignment
DogHouse operator= : [copy-constructed FidoHouse]
contains [Fido], rc = 3
After self-assignment
bobs:[copy-constructed FidoHouse]
contains [Fido], rc = 3
Entering rename("Bob")
After rename("Bob")
DogHouse destructor: [copy-constructed FidoHouse]
contains [Fido], rc = 3
Detaching Dog: [Fido], rc = 3
DogHouse destructor: [FidoHouse assigned]
contains [Fido], rc = 2
Detaching Dog: [Fido], rc = 2
DogHouse destructor: [FidoHouse]
contains [Fido], rc = 1
Detaching Dog: [Fido], rc = 1
Deleting Dog: [Fido], rc = 0
通过对源码的断点跟踪,相信应该不难理解原因……
我也不再赘言……欢迎交流……