[16] 自由存储(Freestore)管理(上)
(Part of C++ FAQ Lite, Copyright ? 1991-2001, Marshall Cline, cline@parashift.com)
简体中文版翻译:申旻,nicrosoft@sunistudio.com(东日制作室,东日文档)
FAQs in section [16]:
- [16.1] delete p 删除指针 p,还是删除指针所指向的数据 *p?
- [16.2] 可以 free() 一个由 new 分配的指针吗?可以 delete 一个由 malloc() 分配的指针吗?
- [16.3] 为什么要用 new 取代原来的值得信赖的 malloc()?
- [16.4] 可以在一个由 new 分配的指针上使用 realloc() 吗?
- [16.5] 需要在 p = new Fred()之后检查NULL 吗?
- [16.6] 我如何确信我的(古老的)编译器会自动检查 new 是否返回NULL?
- [16.7] 在delete p之前需要检查 NULL吗?
- [16.8] delete p 执行了哪两个步骤?
- [16.9] 在 p = new Fred()中,如果Fred构造函数抛出异常,是否会内存“泄漏”?
- [16.10] 如何分配/释放一个对象的数组?
- [16.11] 如果 delete 一个由new T[n]分配的数组,漏了 [] 会如何?
- [16.12] 当delete一个内建类型(char, int, 等) 的数组时,能去掉 [] 吗?
- [16.13] p = new Fred[n]之后,编译器在delete[] p的时候如何知道有 n 个对象被析构?
- [16.14] 成员函数调用delete this合法吗?
- [16.15] 如何用 new 分配多维数组?
- [16.16] 但前一个 FAQ 的代码太技巧而容易出错,有更简单的方法吗?
- [16.17] 但上面的 Matrix 类是针对 Fred的!有办法使它通用吗?
- [16.18] 还有其他方法建立 Matrix 模板吗?
[16.1] delete p 删除指针 p,还是删除指针所指向的数据 *p?
指针指向的数据。
关键字应该是 delete_the_thing_pointed_to_by。同样的情况也发生在 C中释放指针所指的内存: free(p)实际上是指free_the_stuff_pointed_to_by(p)。
[ Top | Bottom | Previous section | Next section ]
[16.2] 可以 free() 一个由 new 分配的指针吗?可以 delete 一个由 malloc() 分配的指针吗?
不!
在一个程序中同时使用 malloc() 和 delete 或者同时使用 new 和 free() 是合情合理合法的。但是,对由 new 分配的指针调用 free(),或对由 malloc() 分配的指针调用 delete,是无理的、非法的、卑劣的。
当心!我偶尔收到一些人的e-mail,他们告诉我在他们的机器 X 上和编译器 Y 上工作正常。但这并不能使得它成为正确的!有时他们说:“但我只是用一下字符数组而已”。即便虽然如此,也不要在同一个指针上混合malloc() 和 delete,或在同一个指针上混合new 和 free()。如果通过p = new char[n]分配,则必须使用delete[] p;不可以使用free(p)。如果通过分配p = malloc(n),则必须使用free(p);不可以使用delete[] p 或 delete p!将它们混合,如果将代码放到新的机器上,新的编译器上,或只是同样编译器的新版本上,都可能导致运行时灾难性的失败。
记住这个警告。
[ Top | Bottom | Previous section | Next section ]
[16.3] 为什么要用 new 取代原来的值得信赖的 malloc()?
构造函数/析构函数,类型安全,可覆盖性(Overridability)。
- 构造函数/析构函数:与 malloc(sizeof(Fred))不一样,new Fred() 调用 Fred 的构造函数。同样,delete p 调用 *p 的析构函数。
- 类型安全:malloc() 返回一个没有类型安全的 void* 。new Fred() 返回一个正确类型(一个 Fred*)的指针。
- 可覆盖性:new 是一个可被类重写/覆盖的算符(operator),而 malloc() 在类上没有可覆盖性。
[ Top | Bottom | Previous section | Next section ]
[16.4] 可以在一个由 new 分配的指针上使用 realloc() 吗?
不可!
realloc() 拷贝时,使用的是位拷贝(bitwise copy )算符,这会打碎许多 C++ 对象。C++对象应该被允许拷贝它们自己。它们使用自己的拷贝构造函数或者赋值算符。
除此之外,new 使用的堆可能和 malloc() 和 realloc() 使用的堆不同!
[ Top | Bottom | Previous section | Next section ]
[16.5] 需要在 p = new Fred()之后检查NULL吗?
[Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.6]:rawtext-->.]不!(但如果你只有旧的编译器,你可能不得不强制 new 算符在内存溢出时抛出一个异常。)
总是在每一个new 调用之后写显式的 NULL 测试实在是非常痛苦的.如下的代码是非常单调乏味的:
Fred* p = new Fred();
if (p == NULL)
throw std::bad_alloc();
如果你的编译器不支持(或如果你拒绝使用)异常, 你的代码可能会更单调乏味:
Fred* p = new Fred();
if (p == NULL) {
std::cerr << "Couldn't allocate memory for a Fred" << endl;
abort();
}
振作一下。在 C++中,如果运行时系统无法为p = new Fred()分配 sizeof(Fred) 字节的内存,会抛出一个 std::bad_alloc 异常。与 malloc()不同,new 永远不会返回 NULL!
因此你只要简单地写:
Fred* p = new Fred(); // 不需要检查 p 是否为 NULL
然而,如果你的编译器很古老,它可能还不支持这个。查阅你的编译器的文档找到“new”。如果你只有古老的编译器,就必须强制编译器拥有这种行为。
[ Top | Bottom | Previous section | Next section ]
[16.6] 我如何确信我的(古老的)编译器会自动检查 new 是否返回 NULL ?
[Recently changed the example to use throw rather than abort() thanks to Stan Brown; changed to use new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.13]:rawtext-->.]最终你的编译器会支持的。
如果你只有古老的不自动执行NULL 测试<!--rawtext:[16.5]:rawtext-->的编译器的话,你可以安装一个“new handler”函数来强制运行时系统来测试。你的“new handler”函数可以作任何你想做的事情,诸如抛出一个异常, delete 一些对象并返回(在operator new会试图再分配的情况下),打印一个消息或者从程序中 abort() 等等。
这里有一个“new handler”的例子,它打印消息并抛出一个异常。它使用 std::set_new_handler() 被安装:
#include <new> // 得到 std::set_new_handler
#include <cstdlib> // 得到 abort()
#include <iostream> // 得到 std::cerr
class alloc_error : public std::exception {
public:
alloc_error() : exception() { }
};
void myNewHandler()
{
// 这是你自己的 handler。它可以做任何你想要做的事情。
throw alloc_error();
}
int main()
{
std::set_new_handler(myNewHandler); // 安装你的 "new handler"
// ...
}
在std::set_new_handler()被执行后,如果/当内存不足时,operator new将调用你的myNewHandler()。这意味着new 不会返回NULL:
Fred* p = new Fred(); // 不需要检查 p 是否为 NULL
注意:如果你的编译器不支持异常处理,作为最后的诉求,你可以将 throw ...; 这一行改为:
std::cerr << "Attempt to allocate memory failed!" << std::endl;
abort();
注意:如果某些全局的/静态的对象的构造函数使用了new,由于它们的构造函数在main()开始之前被调用,因此它不会使用myNewHandler()函数。不幸的是,没有简便的方法确保std::set_new_handler() 在第一次使用 new 之前被调用。例如,即使你将std::set_new_handler()的调用放在全局对象的构造函数中,你仍然无法知道包含该全局对象的模块(“编译单元”)被首先还是最后还是还是中间某个位置被解释。因此,你仍然无法保证std::set_new_handler() 的调用会在任何其他全局对象的构造函数调用之前。
[ Top | Bottom | Previous section | Next section ]
[16.7] 在delete p之前需要检查NULL吗?
不需要!
C++语言担保,如果p等于NULL,则delete p不作任何事情。由于之后可以得到测试,并且大多数的测试方法论都强制显式测试每个分支点,因此你不应该加上多余的 if 测试。
错误的:
if (p != NULL)
delete p;
正确的:
delete p;
[ Top | Bottom | Previous section | Next section ]
[16.8] delete p 执行了哪两个步骤?
delete p 是一个两步的过程:调用析构函数,然后释放内存。delete p产生的代码看上去是这样的(假设是Fred*类型的):
// 原始码:delete p;
if (p != NULL) {
p->~Fred();
operator delete(p);
}
p->~Fred() 语句调用 p 指向的Fred 对象的析构函数。
operator delete(p) 语句调用内存释放原语 void operator delete(void* p)。该原语类似free(void* p)。(然而注意,它们两个不能互换;举例来说,没有谁担保这两个内存释放原语会使用同一个堆!)。
[ Top | Bottom | Previous section | Next section ]
[16.9] 在 p = new Fred() 中,如果Fred 构造函数抛出异常,是否会内存“泄漏”?
不会。
如果异常发生在p = new Fred()的 Fred 构造函数中, C++语言确保已分配的 sizeof(Fred)字节的内存会自动从堆中回收。
这里有两个细节:new Fred()是一个两步的过程:
- sizeof(Fred) 字节的内存使用void* operator new(size_t nbytes)原语被分配。该原语类似于malloc(size_t nbytes)。(然而注意,他们两个不能互换;举例来说,没有谁担保这两个内存分配原语会使用同一个堆!)。
- 它通过调用Fred构造函数在内存中建立对象。第一步返回的指针被作为 this 参数传递给构造函数。这一步被包裹在一个块中以处理这步中抛出异常的情况。
因此实际产生的代码可能是象这样的:
// 原始代码:Fred* p = new Fred();
Fred* p = (Fred*) operator new(sizeof(Fred));
try {
new(p) Fred(); // Placement new<!--rawtext:[11.10]:rawtext-->
} catch (...) {
operator delete(p); // 释放内存
throw; // 重新抛出异常
}
标记为“Placement new”的这句语句调用了 Fred 构造函数。指针 p 成了构占函数 Fred::Fred()内部的this指针。
[ Top | Bottom | Previous section | Next section ]
[16.10] 如何分配/释放一个对象的数组?
使用 p = new T[n] 和 delete[] p:
Fred* p = new Fred[100];
// ...
delete[] p;
任何时候你通过new 来分配一个对象的数组(通常在表达式中有[n]),则在 delete 语句中必须使用[]。该语法是必须的,因为没有什么语法可以区分指向一个对象的指针和指向一个对象数组的指针(从 C 派生出的某些东西)。
[ Top | Bottom | Previous section | Next section ]
[16.11] 如果 delete 一个由new T[n]分配的数组,漏了[]会如何?
所有生命毁灭性地终止。
正确地连接new T[n]和delete[] p是程序员的——不是编译器的——责任。如果你弄错了,编译器会在编译时或运行时给出错误消息。堆(Heap)被破坏是可能的结果,或者更糟糕,你的程序可能会死亡。
[ Top | Bottom | Previous section | Next section ]
[16.12] 当delete一个内建类型 (char, int, 等)的数组时,能去掉 [] 吗?
不行!
有时程序员会认为在delete[] p 中存在[] 仅仅是为了编译器为数组中的每个元素调用适当的析构函数。由于这个原因,他们认为一些内建类型的数组,如 char或int可以不需要[]。举例来说,他们认为以下是合法的代码:
void userCode(int n)
{
char* p = new char[n];
// ...
delete p; // <— 错!应该是 delete[] p !
}
但以上代码是错误的,并且会导致一个运行时的灾难。更详细地来说,delete p调用的是operator delete(void*),而delete[] p调用的是operator delete[](void*)。虽然后者的默认行为是调用前者,但将后者用不同的行为取代是被允许的(这种情况下通常也会将相应的operator new[](size_t)中的 new 取代)。如果被取代的delete[] 代码与delete 代码不兼容,并且调用错误的那个(例如,你写了delete p而不是delete[] p),在运行时可能完蛋。
[ Top | Bottom | Previous section | Next section ]
[16.13] p = new Fred[n]之后,编译器在delete[] p的时候如何知道有个对象被析构?
[Recently changed "popluar" to "popular" thanks to Fabrice Clerc (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.15]:rawtext-->.]精简的回答:魔法。
详细的回答:运行时系统将对象的数量 n 保存在某个通过指针 p 可以获取的地方。有两种普遍的技术来实现。这些技术都在商业编译器中使用,各有权衡,都不完美。这些技术是:
[ Top | Bottom | Previous section | Next section ]
[16.14] 成员函数调用delete this合法吗?
只要你小心,一个对象请求自杀(delete this).是可以的。
以下是我对“小心”的定义:
- 你必须100%的确定,this对象是用 new分配的(不是用new[],也不是用定位放置 new<!--rawtext:[11.10]:rawtext-->,也不是一个栈上的局部对象,也不是全局的,也不是另一个对象的成员,而是明白的普通的new)。
- 你必须100%的确定,该成员函数是this对象最后调用的的成员函数。
- 你必须100%的确定,剩下的成员函数(delete this之后的)不接触到 this对象任何一块(包括调用任何其他成员函数或访问任何数据成员)。
- 你必须 100%的确定,在delete this之后不再去访问this指针。换句话说,你不能去检查它,将它和其他指针比较,和 NULL比较,打印它,转换它,对它做任何事。
自然,对于这种情况还要习惯性地告诫:当你的指针是一个指向基类类型的指针,而没有虚析构函数时(也不可以 delete this)。
[ Top | Bottom | Previous section | Next section ]
[16.15] 如何用new分配多维数组?
[Recently fixed a leak in the third manipulateArray() by moving another for loop into the try block (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.16]:rawtext-->.]有许多方法,取决于你想要让数组有多大的灵活性。一个极端是,如果你在编译时就知道数组的所有的维数,则可以静态地(就如同在C中)分配多维数组:
class Fred { /*...*/ };
void someFunction(Fred& fred);
void manipulateArray()
{
const unsigned nrows = 10; // 行数是编译期常量
const unsigned ncols = 20; // 列数是编译期常量
Fred matrix[nrows][ncols];
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols; ++j) {
// 访问(i,j)元素的方法:
someFunction( matrix[i][j] );
// 可以安全地“返回”,不需要特别的delete代码:
if (today == "Tuesday" && moon.isFull())
return; // 月圆的星期二赶紧退出
}
}
// 在函数末尾也没有显式的delete代码
}
更一般的,矩阵的大小只有到运行时才知道,但确定它是一个矩形。这种情况下,你需要使用堆(“自由存储”)(heap,freestore),但至少你可以把所有元素非胚在自由存储块中。
void manipulateArray(unsigned nrows, unsigned ncols)
{
Fred* matrix = new Fred[nrows * ncols];
// 由于我们上面使用了简单的指针,因此我们需要非常
// 小心避免漏过 delete 代码。
// 这就是为什么要捕获所有异常:
try {
// 访问(i,j) 元素的方法:
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols; ++j) {
someFunction( matrix[i*ncols + j] );
}
}
// 如果你想在月圆的星期二早点退出,
// 就要确保在返回的所有途径上做 delete :
if (today == "Tuesday" && moon.isFull()) {
delete[] matrix;
return;
}
// ...
}
catch (...) {
// 确保在异常抛出后delete :
delete[] matrix;
throw; // 重新抛出当前异常
}
// 确保在函数末尾也做了 delete :
delete[] matrix;
}
最后是另一个极端,你可能甚至不确定矩阵是矩形的。例如,如果每行可以有不同的长度,你就需要为个别地分配每一行。在如下的函数中,ncols[i] 是第 i 行的列数,i 的可变范围是 0 到 nrows-1。
void manipulateArray(unsigned nrows, unsigned ncols[])
{
typedef Fred* FredPtr;
// 如果后面抛出异常,不要成为漏洞:
FredPtr* matrix = new FredPtr[nrows];
// 以防万一稍后会有异常,将每个元素设置为 NULL:
// (见 try 块顶端的注释。)
for (unsigned i = 0; i < nrows; ++i)
matrix[i] = NULL;
// 由于我们上面使用了简单的指针,我们需要
// 非常小心地避免漏过delete 代码。
// 这就是为什么我们要捕获所有的异常:
try {
// 接着我们组装数组。如果其中之一抛出异常,所有的
// 已分配的元素都会被释放 (见如下的 catch )。
for (unsigned i = 0; i < nrows; ++i)
matrix[i] = new Fred[ ncols[i] ];
// 访问(i,j) 元素的方法:
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols[i]; ++j) {
someFunction( matrix[i][j] );
}
}
// 如果你想在月圆的星期二早些退出,
// 确保在返回的所有途径上做 delete:
if (today == "Tuesday" && moon.isFull()) {
for (unsigned i = nrows; i > 0; --i)
delete[] matrix[i-1];
delete[] matrix;
return;
}
// ...
}
catch (...) {
// 确保当有异常抛出时做 delete :
// 注意 matrix[...] 中的一些指针可能是
// NULL, 但由于delete NULL是合法的,所以没问题。
for (unsigned i = nrows; i > 0; --i)
delete[] matrix[i-1];
delete[] matrix;
throw; // 重新抛出当前异常
}
// 确保在函数末尾也做 delete :
// 注意释放与分配反向:
for (unsigned i = nrows; i > 0; --i)
delete[] matrix[i-1];
delete[] matrix;
}
注意释放过程中 matrix[i-1]的使用。这样可以防止无符号值 i 的步进为小于0 的回绕。
最后,注意指针和数组是会带来麻烦的。通常,最好将你的指针封装在一个有着安全的和简单的接口的类中。下一个FAQ告诉你如何这样做。
[ Top | Bottom | Previous section | Next section ]
[16.16] 但前一个FAQ的代码太技巧容易出错!有更简单的方法吗?
[Recently clarified the last paragraph (on 7/00) and fixed the Star Trek movie number thanks to Chris Sheppard (on 4/01) and wordsmithed last paragraph at the suggestion of prapp (on 4/01). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.17]:rawtext-->.]有。
前一个FAQ之所以太过技巧而容易出错是因为它使用了指针,我们知道指针和数组会带来麻烦。解决办法是将指针封装到一个有着安全的和简单的接口的类中。例如,我们可以定义一个 Matrix 类来处理矩形的矩阵,用户代码将比前一个FAQ中的矩形矩阵的代码简单得多:
// Matrix 类的代码在下面显示...
void someFunction(Fred& fred);
void manipulateArray(unsigned nrows, unsigned ncols)
{
Matrix matrix(nrows, ncols); // 构造一个 matrix
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols; ++j) {
// 访问(i,j) 元素的方法:
someFunction( matrix(i,j) );
// 你可以不用写任何的 delete 代码安全地“返回”:
if (today == "Tuesday" && moon.isFull())
return; // 月圆的星期二早些退出
}
}
// 在函数末尾也没有显式的delete代码
}
需要注意的主要是整理后的代码的短小。例如,再如上的代码中没有任何 delete 语句,也不会有内存泄漏,这个假设仅仅是基于析构函数正确地完成它的工作。
以下就是使得以上成为可能的Matrix的代码:
class Matrix {
public:
Matrix(unsigned nrows, unsigned ncols);
// 如果任何一个尺寸为 0,则抛出 BadSize 对象的异常:
class BadSize { };
// 基于大三法则(译注:即三者须同时存在):
~Matrix();
Matrix(const Matrix& m);
Matrix& operator= (const Matrix& m);
// 取得 (i,j) 元素的访问方法:
Fred& operator() (unsigned i, unsigned j);
const Fred& operator() (unsigned i, unsigned j) const;
// 如果i 或j 太大,抛出BoundsViolation 对象
class BoundsViolation { };
private:
Fred* data_;
unsigned nrows_, ncols_;
};
inline Fred& Matrix::operator() (unsigned row, unsigned col)
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row*ncols_ + col];
}
inline const Fred& Matrix::operator() (unsigned row, unsigned col) const
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row*ncols_ + col];
}
Matrix::Matrix(unsigned nrows, unsigned ncols)
: data_ (new Fred[nrows * ncols]),
nrows_ (nrows),
ncols_ (ncols)
{
if (nrows == 0 || ncols == 0)
throw BadSize();
}
Matrix::~Matrix()
{
delete[] data_;
}
注意以上的Matrix类完成两件事:将技巧性的内存管理代码从客户代码(例如,main())移到类中,并且总体上减少了编程。这第二点很重要。例如,假设 Matrix有略微的可重用性,将复杂性从Matrix的用户们[复数]处移到了Matrix自身[单数]就等于将复杂性从多的方面移到少的方面。任何看过星际旅行2的人都知道多数的利益高于少数或者个体的利益。
[ Top | Bottom | Previous section | Next section ]
[16.17] 但上面的Matrix类是针对Fred的!有办法使它通用吗?
[Recently rewrote (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.18]:rawtext-->.]有;那就是使用模板:
以下就是如何能用模板:
#include "Fred.hpp" // 得到Fred类的定义
// Matrix<T> 的代码在后面显示...
void someFunction(Fred& fred);
void manipulateArray(unsigned nrows, unsigned ncols)
{
Matrix<Fred> matrix(nrows, ncols); // 构造一个称为matrix的 Matrix<Fred>
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols; ++j) {
// 访问 (i,j) 元素的方法:
someFunction( matrix(i,j) );
// 你可以不用任何的delete 的代码安全地“返回”:
if (today == "Tuesday" && moon.isFull())
return; // 月圆的星期二早些退出
}
}
// 函数末尾也没有显式的delete代码
}
现在很容易为非 Fred 的类使用 Matrix<T>。例如,以下为std::string 使用一个 Matrix (std::string 是标准字符串类):
#include <string>
void someFunction(std::string& s);
void manipulateArray(unsigned nrows, unsigned ncols)
{
Matrix<std::string> matrix(nrows, ncols); // 构造一个 Matrix<std::string>
for (unsigned i = 0; i < nrows; ++i) {
for (unsigned j = 0; j < ncols; ++j) {
// 访问 (i,j) 元素的方法:
someFunction( matrix(i,j) );
// 你可以不用任何的delete 的代码安全地“返回”:
if (today == "Tuesday" && moon.isFull())
return; // 月圆的星期二早些退出
}
}
// 函数末尾也没有显式的delete代码
}
因此,你可以从模板得到类的完整家族。例如, Matrix<Fred>, Matrix<std::string>, Matrix< Matrix<std::string> >等等。
以下是实现该模板的一种方法:
template<class T> // 详见 模板一节
class Matrix {
public:
Matrix(unsigned nrows, unsigned ncols);
// 如果任何一个尺寸为 0,则抛出 BadSize 对象
class BadSize { };
// 基于大三法则(译注:即三者须同时存在):
~Matrix();
Matrix(const Matrix<T>& m);
Matrix<T>& operator= (const Matrix<T>& m);
// 获取 (i,j) 元素的访问方法:
T& operator() (unsigned i, unsigned j);
const T& operator() (unsigned i, unsigned j) const;
// 如果 i 或 j 太大,则抛出 BoundsViolation 对象
class BoundsViolation { };
private:
T* data_;
unsigned nrows_, ncols_;
};
template<class T>
inline T& Matrix<T>::operator() (unsigned row, unsigned col)
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row*ncols_ + col];
}
template<class T>
inline const T& Matrix<T>::operator() (unsigned row, unsigned col) const
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row*ncols_ + col];
}
template<class T>
inline Matrix<T>::Matrix(unsigned nrows, unsigned ncols)
: data_ (new T[nrows * ncols])
, nrows_ (nrows)
, ncols_ (ncols)
{
if (nrows == 0 || ncols == 0)
throw BadSize();
}
template<class T>
inline Matrix<T>::~Matrix()
{
delete[] data_;
}
[ Top | Bottom | Previous section | Next section ]
[16.18] 还有其他方法建立 Matrix 模板吗?
[Recently created thanks to Jesper Rasmussen (on 4/01). Click here to go to the next FAQ in the "chain" of recent changes<!--rawtext:[16.19]:rawtext-->.]用标准的vector 模板,制作一个向量的向量。
以下代码使用了一个vector<vector<T> >(注意两个 > 符号之间的空格)。
#include <vector>
template<class T> // 详见模板一节
class Matrix {
public:
Matrix(unsigned nrows, unsigned ncols);
// 如果任何的尺寸为 0,抛出 BadSize 对象
class BadSize { };
// 不需要大三法则!
// 得到 (i,j) 元素的访问方法:
T& operator() (unsigned i, unsigned j);
const T& operator() (unsigned i, unsigned j) const;
// 如果 i 或 j 太大,则抛出 BoundsViolation 对象
class BoundsViolation { };
private:
vector<vector<T> > data_;
};
template<class T>
inline T& Matrix<T>::operator() (unsigned row, unsigned col)
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row][col];
}
template<class T>
inline const T& Matrix<T>::operator() (unsigned row, unsigned col) const
{
if (row >= nrows_ || col >= ncols_) throw BoundsViolation();
return data_[row][col];
}
template<class T>
Matrix<T>::Matrix(unsigned nrows, unsigned ncols)
: data_ (nrows)
{
if (nrows == 0 || ncols == 0)
throw BadSize();
for (unsigned i = 0; i < nrows; ++i)
data_[i].resize(ncols);
}
[ Top | Bottom | Previous section | Next section ]