2008年10月14日星期二

[TIC++] C11. References & the Copy-Constructor

代码阅读<Thinking In C++>
Chapter 11. References & the Copy-Constructor

一、再次介绍引用

// reference.cpp
int *f(int *x)
{
(*x)++;
return x;
}

int &g(int &x)
{
x++;
return x;
}

int &h()
{
int q;
//! return q; // error
static int x;
return x;
}

int main()
{
int a = 0;
f(&a);
g(a);
}

reference可以被简单视作一个常量指针,而这个指针会在预编译期间自动被解释掉。
ref最常用到的地方,就是作为参数和返回值使用了。对参数而言,任何形参的改变会作用
到实参;针对返回值,要注意的是返回的东西只是引用,必须保证要返回的内存在返回地可
见。
* 因此,你不能在h()中返回局部变量。

在介绍copy constructor之前,让我们来看常引用情况 ————

// const_ref.cpp
void f(int&) {}
void g(const int&) {}

int main() {
//! f(1); //invalid initialization of non-const reference
g(1);
}

当传递1这个实参的时候,编译器会为1分配内存,然后创造一个引用(int&)绑定到这个内存
地址。如果1没有被声明为const,那将是毫无意义的。f(1)将得到一个编译时错误。

最后是复合化的引用,指向指针的引用 ————

// c11: ref_to_pointer.cpp
#include <iostream>
using namespace std;

void increment(int* &i) { i++; }

int main()
{
int *i = 0;

cout << "i = " << i << endl;
increment(i);
cout << "i = " << i << endl;
}

这里阵阵改变的是指针(int*)i,而不是指针指向的值,但是这里传递的i。
在C中要做到这点,那就要使用二级指针了。

二、copy-constructor

// c11: howmany.cpp
#include <fstream>
#include <string>

using namespace std;
ofstream out("howmany.out");

class howmany {
static int object_cnt;
public:
howmany() { object_cnt++; }
static void print(const string& msg = "") {
if (msg.size() != 0)
out << msg << ": ";
out << "object_cnt = " << object_cnt << endl;
}
~howmany() {
object_cnt--;
print("~howmany()");
}
};

int howmany::object_cnt = 0;

// pass and return by value:
howmany f(howmany x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany h;
howmany::print("after construction of h");
howmany h2 = f(h);
howmany::print("after call to f()");
}
/* result:
after construction of h: object_cnt = 1
x argument inside f(): object_cnt = 1
~howmany(): object_cnt = 0
after call to f(): object_cnt = 0
~howmany(): object_cnt = -1
~howmany(): object_cnt = -2
*/

首先我们构建howmany h,调用构造函数,会得到object_cnt = 1;
然后我们使用f()来构建第二个对象h2 = f(h),编译器传递h拷贝,并没有使用构造函数来
创建h2。但是意想不到的是,离开f()的时候发生了析构,导致object_cnt = 0。
而最后的两个析构,使得object_cnt得到负数。

这是为什么呢?
因为在f()内,函数使用了C形式的bitcopy,我们得到的只是一份h的拷贝,他没有调用构造
方法,所以我们没有看到f()中objecgt_cnt = 2。而在后来,C++特性去保证了最后一步析
构,因此出现了object_cnt不升反降的结果。

C++中为了保证构造的完整性,因此提出了copy-constructor————

// c11: howmany.cpp
#include <fstream>
#include <string>
using namespace std;

ofstream out("howmany2.out");

class howmany2 {
string name;
static int object_cnt;
public:
howmany2(const string &id = ""): name(id) {
++object_cnt;
print("howmany2()");
}
~howmany2() {
--object_cnt;
print("~howmany()");
}
howmany2(const howmany2 &h): name(h.name) {
name += " copy";
++object_cnt;
print("howmany2(const howmany2&)");
}
void print(const string& msg = "") const {
if (msg.size() != 0)
out << msg << endl;
out << '\t' << name << ": "
<< "object_cnt = " << object_cnt << endl;
}
};

int howmany2::object_cnt = 0;

// pass and return by value:
howmany2 f(howmany2 x)
{
x.print("x argument inside f()");
return x;
}

int main()
{
howmany2 h("h");
out << "entering f()" << endl;
howmany2 h2 = f(h);
h2.print("h2 after call to f()");
out << "call f(), no return value" << endl;
f(h);
out << "after call to f()" << endl;
}
/*
howmany2()
h: object_cnt = 1
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
h2 after call to f()
h copy copy: object_cnt = 2
call f(), no return value
howmany2(const howmany2&)
h copy: object_cnt = 3
x argument inside f()
h copy: object_cnt = 3
howmany2(const howmany2&)
h copy copy: object_cnt = 4
~howmany()
h copy copy: object_cnt = 3
~howmany()
h copy: object_cnt = 2
after call to f()
~howmany()
h copy copy: object_cnt = 1
~howmany()
h: object_cnt = 0
*/

我们可以看到,f()传递参数的时候,编译器将会把拷贝传值的过程交给copy-constructor,
同样return返回值,也需要这样的拷贝。
entering f()
howmany2(const howmany2&)
h copy: object_cnt = 2
x argument inside f()
h copy: object_cnt = 2
howmany2(const howmany2&)
h copy copy: object_cnt = 3

而后离开f(),这需要析构刚才创建的临时变量,保留return的值。

第二次f()没有返回值,因此情况稍微发生改变。此种调用忽略了返回值,编译器也会聪明
的直接在调用点析构此临时对象。

三、选择copy-construction
我们来看如何迫使系统自建拷贝构造方法

// c11: default_cc.cpp
// automatic creation of the copy-constructor
#include <iostream>
#include <string>
using namespace std;

class withcc {
public:
withcc() {}
withcc(const withcc &) {
cout << "withcc(withcc &)" << endl;
}
};

class wocc {
string id;
public:
wocc(const string &ident = ""): id(ident) {}
void print(const string &msg = "") const {
if (msg.size() != 0)
cout << msg << ": ";
cout << id << endl;
}
};

class composite {
withcc tc;
wocc oc;
public:
composite(): oc("composite()") {}
void print(const string &msg = "") const {
oc.print(msg);
}
};

int main() {
composite c;
c.print("content of c");
cout << "calling composite copy-constructor" << endl;
composite c2 = c;
c2.print("content of c2");
}
/* reuslt
content of c: composite()
calling composite copy-constructor
withcc(withcc &)
content of c2: composite()
*/

我们在这里看到两个案例:
1) class withcc拥有构造方法和拷贝构造方法。一旦包含拷贝构造函数,这就是在告诉编
译器:永远不要再自作主张,自动创建默认构造函数。因此,如果class withcc没有定义
withcc(),编译器会报错——composite中的tc没有办法创建。

2) class composite拥有构造方法,而没有拷贝构造方法。一旦需要拷贝构造方法,编译器
会为所有的成员对象调用他们的拷贝构造方法。(这通过组合或者继承可以实现composite)
在这里,c2 = c,就强迫调用了withcc和wocc的拷贝构造方法。而wocc没有拷贝构造方法,
因此系统为其自动定义一个,完成bitcopy。

接下来,通过小技巧来避免赋值拷贝

// c10: no_cc.cpp
class nocc {
int i;
nocc(const nocc &);
public:
nocc(int ii = 0): i(ii) {}
};

void f(nocc);

int main(void)
{
nocc n;
// copy-constructor is private!
//! f(n);
//! nocc n2 = n;
//! nocc n3(n);
}

通过将拷贝构造方法设置为私有,来避免赋值拷贝,这是一个非常简单的方法。
在此不用赘述。
_

没有评论:

发表评论