C++ 的引用类型
在翻旧文的时候,发现这么一篇文章:关于一道C++笔试题的纠结,学计算机的伤不起啊。当时可能是觉得 Placement new 的语法[1]比较新鲜,所以印象比较深刻。现在则是觉得那篇文章中的笔试题挺有水准的,于是记一篇文章,特别说明这一问题——C++ 的引用类型。
C++ 的类型默认是值类型
C++ 的 class 默认是值类型的。我们常常会从内存布局的角度来看待一个值类型。默认情况下,值类型可以复制,也就是说,值类型具有一个拷贝构造函数以及一个拷贝赋值操作符。值类型对象反映的是其内容,当拷贝发生时,总是会产生两个相互独立的对象,修改其中的一个对象不会改变另一个对象的内容。
C++ 也可以定义引用类型
我们可以通过一定的设置,构造出 C++ 的引用类型,这样的类型具有多态的行为方式,从而可以支持面向对象编程。我们总是以实现多态行为为目的来看待引用类型的,比如说其基类是什么,有没有虚函数,等等。我们应当禁用一个引用类型的拷贝构造函数以及拷贝赋值操作符,并且使用虚析构函数。引用类型的对象反映的是其身份——这是对什么对象的引用?因此,引用类型也常被成为多态类型。
使用 C++ 定义引用类型
虽然C#里面的引用类型用的都是指针或者是引用,但是 C++ 中的引用类型并不受此限制。但是在传递一个引用类型的对象时,只能传递引用或指针,而不能传递值,否则与引用类型的意图不符合。
因此我们需要做的就是:
-
禁用拷贝构造函数。
-
禁用拷贝赋值操作符。
-
显式提供一个构造函数。
-
显式提供一个虚析构函数。
示例代码如下所示:
class MyRefType
{
private:
MyRefType(const MyRefType &);
MyRefType & operator=(const MyRefType &);
public:
MyRefType() { }
virtual ~MyRefType() { }
};
这样一来,我们就定义了一个引用类型的对象MyRefType
。注意,MyRefType
可以分配在栈上,也可以分配在堆上,这一点是与
C#、Java
等语言不同的。但是,如果需要传递`MyRefType`的对象,则必须传递引用或者是指针,否则将会出现编译错误:(错误信息依编译器不同而变化)
Cannot access private member declared in class
MyRefType
.
测试用例如下:
MyRefType CreateMyRefTypeInstance();
int main(void)
{
MyRefType instance1;
MyRefType instance2 = instance1;
MyRefType instance3 = CreateMyRefTypeInstance();
return 0;
}
其中第 19 行和第 20 行编译时期会报错。
C++11 的新语法
class MyRefType
{
MyRefType(const MyRefType &) = delete;
MyRefType & operator=(const MyRefType &) = delete;
public:
MyRefType() = default;
virtual ~MyRefType() = default;
};
个人认为,使用这种方式来实现引用对象,代码可读性要上一个台阶。
但是 C++11 提出了一个叫做 Move 的新语义,可以用于转移对象的所有权。对此有何影响,还不可知。Bjarne Stroustrup 的 C++11 FAQ 中的 control of defaults: move and copy 中提到了一些这方面的 建议。
禁止使用栈分配对象
下面绕来绕去又回到了那个 笔试题,即如何禁止使用栈分配对象?一般说来有两种方法:限制访问构造函数或者限制访问析构函数。并且同时,我们应该把这个对象视为一个引用类型的对象,否则我们就可以合理的创建一个在栈上的拷贝。印象中
Bjarne Stroustrup 在 The Design and Evolution of C++
中曾经提到过,将析构函数设为 protected
(未求证),就像这样:
class HeapOnlyRefType1
{
private:
HeapOnlyRefType1(const HeapOnlyRefType1 &);
HeapOnlyRefType1 & operator=(const HeapOnlyRefType1 &);
public:
HeapOnlyRefType1() { }
void destory(void) { delete this; }
protected:
virtual ~HeapOnlyRefType1() { }
};
这样一来,由于不能使用析构函数,自然就不能在栈上创建该对象了。需要注意的是,该类型的全局变量和临时对象也不能创建,因为它们最终还是要被销毁的,但是编译器却不能使用析构函数。为了避免内存泄漏,我们使用`destory`方法来销毁这一类型的对象。
另一种方式,具有更高的灵活性,也是我比较习惯使用的一种方式,即通过禁用构造函数来限制这一对象,就像这样:
class HeapOnlyRefType2
{
private:
HeapOnlyRefType2(const HeapOnlyRefType2 &);
HeapOnlyRefType2 & operator=(const HeapOnlyRefType2 &);
protected:
HeapOnlyRefType2() { }
public:
static HeapOnlyRefType2 * CreateInstance(void) { return new HeapOnlyRefType2(); }
virtual ~HeapOnlyRefType2() { }
};
这样一来,今后我们还可以通过修改实例方法,来控制该类型对象的创建。比如说使用单例模式(Singleton Pattern)或者其他创建模式,或者使用一些对象策略,比如说缓存,等等。
参考资料
-
[1] C++0x/C++11 Support in GCC - GNU Project - Free Software Foundation (FSF)[EB/OL]. [2012-11-23]. http://gcc.gnu.org/projects/cxx0x.html.
-
[2] C++11 Features (Modern C++)[EB/OL]. [2012-11-23]. http://msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx.
-
[3] 陈皓. C++11 中值得关注的几大变化(详解)[EB/OL]. [2012-11-23]. http://coolshell.cn/articles/5265.html.
-
[4] More C++ Idioms/Requiring or Prohibiting Heap-based Objects[J]. Wikibooks, open books for an open world.
-
[5] Placement syntax[J]. Wikipedia, the free encyclopedia, 2012.
-
[6] STROUSTRUP B. The Design and Evolution of C++[M]. 第1版. Addison-Wesley Professional, 1994.
-
[7] Value Types (Modern C++)[EB/OL]. [2012-11-23]. http://msdn.microsoft.com/en-us/library/vstudio/hh438479.aspx.
-
[8] What’s New for Visual C++ in Visual Studio 2012[EB/OL]. [2012-11-23]. http://msdn.microsoft.com/en-us/library/hh409293.aspx.
-
[9] 赵昱. 关于一道C++笔试题的纠结,学计算机的伤不起啊[EB/OL]. [2012-11-23]. http://www.cnblogs.com/icyplayer/archive/2011/06/23/2087965.html.
2012/12/3 补充:现在最新版本的Visual Studio 2012 Update 1 已经支持[3]中所提到的语法了。