| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 12984 人关注过本帖, 3 人收藏
标题:[分享]C++的一些FAQ
取消只看楼主 加入收藏
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
我应该如何对付内存泄漏?

写出那些不会导致任何内存泄漏的代码。很明显,当你的代码中到处充满了 new 操作、
delete操作和指针运算的话,你将会在某个地方搞晕了头,导致内存泄漏,指针引用错误,
以及诸如此类的问题。这和你如何小心地对待内存分配工作其实完全没有关系:代码的复杂
性最终总是会超过你能够付出的时间和努力。于是随后产生了一些成功的技巧,它们依赖于
将内存分配(allocations)与重新分配(deallocation)工作隐藏在易于管理的类型
之后。标准容器(standard containers)是一个优秀的例子。它们不是通过你而是自
己为元素管理内存,从而避免了产生糟糕的结果。想象一下,没有 string 和 vector 的
帮助,写出这个:

#include<vector>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;

int main() // small program messing around with strings
{
cout << "enter some whitespace-separated words:\n";
vector<string> v;
string s;
while (cin>>s) v.push_back(s);

sort(v.begin(),v.end());

string cat;
typedef vector<string>::const_iterator Iter;
for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";
cout << cat << '\n';
}

你有多少机会在第一次就得到正确的结果?你又怎么知道你没有导致内存泄漏呢?

注意,没有出现显式的内存管理,宏,造型,溢出检查,显式的长度限制,以及指针。通过
使用函数对象和标准算法(standard algorithm),我可以避免使用指针——例如使用
迭代子(iterator),不过对于一个这么小的程序来说有点小题大作了。

这些技巧并不完美,要系统化地使用它们也并不总是那么容易。但是,应用它们产生了惊人
的差异,而且通过减少显式的内存分配与重新分配的次数,你甚至可以使余下的例子更加容
易被跟踪。早在 1981 年,我就指出,通过将我必须显式地跟踪的对象的数量从几万个减少
到几打,为了使程序正确运行而付出的努力从可怕的苦工,变成了应付一些可管理的对象,
甚至更加简单了。

如果你的程序还没有包含将显式内存管理减少到最小限度的库,那么要让你程序完成和正确
运行的话,最快的途径也许就是先建立一个这样的库。

模板和标准库实现了容器、资源句柄以及诸如此类的东西,更早的使用甚至在多年以前。异
常的使用使之更加完善。

如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时,你可以使用资源句
柄(resource handle),以将内存泄漏的可能性降至最低。这里有个例子:我需要通过
一个函数,在空闲内存中建立一个对象并返回它。这时候可能忘记释放这个对象。毕竟,我
们不能说,仅仅关注当这个指针要被释放的时候,谁将负责去做。使用资源句柄,这里用了
标准库中的 auto_ptr,使需要为之负责的地方变得明确了。

#include<memory>
#include<iostream>
using namespace std;

struct S {
S() { cout << "make an S\n"; }
~S() { cout << "destroy an S\n"; }
S(const S&) { cout << "copy initialize an S\n"; }
S& operator=(const S&) { cout << "copy assign an S\n"; }
};

S* f()
{
return new S; // 谁该负责释放这个 S?
};

auto_ptr<S> g()
{
return auto_ptr<S>(new S); // 显式传递负责释放这个S
}

int main()
{
cout << "start main\n";
S* p = f();
cout << "after f() before g()\n";
// S* q = g(); // 将被编译器捕捉
auto_ptr<S> q = g();
cout << "exit main\n";
// *p产生了内存泄漏
// *q被自动释放
}

在更一般的意义上考虑资源,而不仅仅是内存。

如果在你的环境中不能系统地应用这些技巧(例如,你必须使用别的地方的代码,或者你的
程序的另一部分简直是原始人类(译注:原文是 Neanderthals,尼安德特人,旧石器时
代广泛分布在欧洲的猿人)写的,如此等等),那么注意使用一个内存泄漏检测器作为开发
过程的一部分,或者插入一个垃圾收集器(garbage collector)。

2006-07-06 17:18
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
我为什么在捕获一个异常之后就不能继续?

换句话说,C++为什么不提供一种简单的方式,让程序能够回到异常抛出点之后,并继续执
行?

主要的原因是,如果从异常处理之后继续,那么无法预知掷出点之后的代码如何对待异常处
理,是否仅仅继续执行,就象什么也没有发生一样。异常处理者无法知道,在继续之前,有
关的上下文环境(context)是否是“正确”的。要让这样的代码正确执行,抛出异常的
编写者与捕获异常的编写者必须对彼此的代码与上下文环境都非常熟悉才行。这样会产生非
常复杂的依赖性,因此无论在什么情况下,都会导致一系列严重的维护问题。

当我设计C++的异常处理机制时,我曾经认真地考虑过允许这种继续的可能性,而且在标准
化的过程中,这个问题被非常详细地讨论过。请参见《C++语言的设计和演变》中的异常处
理章节。


2006-07-06 17:19
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
怎样从输入中读取一个字符串?

你可以用这种方式读取一个单独的以空格结束的词:

#include<iostream>
#include<string>
using namespace std;

int main()
{
cout << "Please enter a word:\n";

string s;
cin>>s;

cout << "You entered " << s << '\n';
}

注意,这里没有显式的内存管理,也没有可能导致溢出的固定大小的缓冲区。

如果你确实想得到一行而不是一个单独的词,可以这样做:


#include<iostream>
#include<string>
using namespace std;

int main()
{
cout << "Please enter a line:\n";

string s;
getline(cin,s);

cout << "You entered " << s << '\n';
}


2006-07-06 17:20
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么 C++不提供“finally”的构造?

因为 C++提供了另外一种方法,它几乎总是更好的:“资源获得即初始化”(resource
acquisiton is initialization)技术。基本的思路是,通过一个局部对象来表现资
源,于是局部对象的析构函数将会释放资源。这样,程序员就不会忘记释放资源了。举例来
说:

class File_handle {
FILE* p;
public:
File_handle(const char* n, const char* a)
{ p = fopen(n,a); if (p==0) throw Open_error(errno); }
File_handle(FILE* pp)
{ p = pp; if (p==0) throw Open_error(errno); }

~File_handle() { fclose(p); }

operator FILE*() { return p; }

// ...
};

void f(const char* fn)
{
File_handle f(fn,"rw"); //打开fn进行读写
// 通过f 使用文件
}

在一个系统中,需要为每一个资源都使用一个“资源句柄”类。无论如何,我们不需要为每
一个资源获得都写出“finally”语句。在实时系统中,资源获得要远远多于资源的种类,
因此和使用“finally”构造相比,“资源获得即初始化”技术会产生少得多的代码。


2006-07-06 17:20
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么我不能重载点符号,::,sizeof,等等?

大多数的运算符能够被程序员重载。例外的是:

. (点符号) :: ?: sizeof

并没有什么根本的原因要禁止重载?:。仅仅是因为,我没有发现有哪种特殊的情况需要重
载一个三元运算符。注意一个重载了 表达式1?表达式2:表达式 3 的函数,不能够保证
表达式 2:表达式3 中只有一个会被执行。

Sizeof 不能够被重载是因为内建的操作(built-in operations),诸如对一个指向
数组的指针进行增量操作,必须依靠它。考虑一下:

X a[10];
X* p = &a[3];
X* q = &a[3];
p++; // p指向a[4]
// 那么p 的整型值必须比 q的整型值大出一个 sizeof(X)

所以,sizeof(X)不能由程序员来赋予一个不同的新意义,以免违反基本的语法。

在 N::m 中,无论 N 还是 m 都不是值的表达式;N 和 m 是编译器知道的名字,::执行一个
(编译期的)范围解析,而不是表达式求值。你可以想象一下,允许重载 x::y的话,x 可
能是一个对象而不是一个名字空间(namespace)或者一个类,这样就会导致——与原来
的表现相反——产生新的语法(允许 表达式 1::表达式 2)。很明显,这种复杂性不会带
来任何好处。

理论上来说,.(点运算符)可以通过使用和->一样的技术来进行重载。但是,这样做会导
致一个问题,那就是无法确定操作的是重载了.的对象呢,还是通过.引用的一个对象。例
如:


class Y {
public:
void f();
// ...
};

class X { // 假设你能重载.
Y* p;
Y& operator.() { return *p; }
void f();
// ...
};

void g(X& x)
{
x.f(); // X::f还是Y::f还是错误?
}

这个问题能够用几种不同的方法解决。在标准化的时候,哪种方法最好还没有定论。更多的
细节,请参见《C++语言的设计和演变》。

2006-07-06 17:20
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
使用宏有什么问题?

宏不遵循C++中关于范围和类型的规则。这经常导致一些微妙的或不那么微妙的问题。因此,
C++提供更适合其他的 C++(译注:原文为 the rest of C++,当指 C++除了兼容 C 以
外的部分)的替代品,例如内联函数、模板与名字空间。

考虑一下:

#include "someheader.h"

struct S {
int alpha;
int beta;
};

如果某人(不明智地)地写了一个叫“alpha”或“beta”的宏,那么它将不会被编译,
或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含:

#define alpha 'a'
#define beta b[2]

将宏(而且仅仅是宏)全部大写的习惯,会有所帮助,但是对于宏并没有语言层次上的保护
机制。例如,虽然成员的名字包含在结构体的内部,但这无济于事:在编译器能够正确地辨
别这一点之前,宏已经将程序作为一个字符流进行了处理。顺便说一句,这是 C 和 C++程
序开发环境和工具能够被简化的一个主要原因:人与编译器看到的是不同的东西。

不幸的是,你不能假设别的程序员总是能够避免这种你认为“相当白痴”的事情。例如,最
近有人报告我,他们遇到了一个包含 goto 的宏。我也见过这种情况,而且听到过一些——
在很脆弱的时候——看起来确实有理的意见。例如:

#define prefix get_ready(); int ret__
#define Return(i) ret__=i; do_something(); goto exit
#define suffix exit: cleanup(); return ret__

void f()
{
prefix;
// ...
Return(10);
// ...
Return(x++);
//...
suffix;
}

作为一个维护的程序员,就会产生这种印象;将宏“隐藏”到一个头文件中——这并不罕见
——使得这种“魔法”更难以被辨别。

一个常见的微妙问题是,一个函数风格的宏并不遵守函数参数传递的规则。例如:

#define square(x) (x*x)

void f(double d, int i)
{
square(d); // 好
square(i++); // 糟糕:这表示 (i++*i++)
square(d+1); //糟糕:这表示(d+1*d+1); 也就是 (d+d+1)
// ...
}

“d+1”的问题,可以通过在“调用”时或宏定义时添加一对圆括号来解决:

#define square(x) ((x)*(x)) /*这样更好 */

但是, i++被执行了两次(可能并不是有意要这么做)的问题仍然存在。

是的,我确实知道有些特殊的宏并不会导致 C/C++预处理宏这样的问题。但是,我无心去
发展 C++中的宏。作为替代,我推荐使用 C++语言中合适的工具,例如内联函数,模板,
构造函数(用来初始化),析构函数(用来清除),异常(用来退出上下文环境),等等。

2006-07-06 17:21
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
给置顶了,那有空就翻翻以前的贴子,看看有什么经常性的问题,弄个索引什么之类的。

---------------------------------------------

晕,水平不够,分辨不出来。

[此贴子已经被作者于2006-7-9 9:10:28编辑过]


2006-07-07 10:38
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
p1 是void*型的指针,可以指向其他类型。

2006-07-08 09:13
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
我可以通过编译,编译器的问题可能。

static const int a = 10;

a是静态常量,属于类的常量,只有一份
你那样写,的常量属于类对象,有多少非静态的对象,就有多少个。

2006-08-26 12:30
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
恩恩,回了

2006-10-29 19:03
快速回复:[分享]C++的一些FAQ
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.045452 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved