Effect++好像说过(要不就是exception c++)勿在构造函数中抛出异常,所以这个struct MyRAII 有问题。google的c++代码规范明确的说构造函数别复杂,宁可用一个init来替代(不是构造中调init啊,而是外部调)
Init() method.Definition:It is possible to perform initialization in the body of the constructor.
Pros:Convenience in typing. No need to worry about whether the class has been initialized or not.
Cons:The problems with doing work in constructors are:
main(), possibly breaking some implicit assumptions in the constructor code. For instance,gflags will not yet have been initialized.Decision:If your object requires non-trivial initialization, consider having an explicit Init() method. In particular, constructors should not call virtual functions, attempt to raise errors, access potentially uninitialized global variables, etc.
这是错的,应该用 throw; 代替 throw e;
> 2011/9/9 up duan <fixo...@gmail.com>
>
> > 既然我们的祖先已经学会了采矿、冶炼、制斧,那么我们砍树的时候就不应该跑到火山旁边捡两块黑曜岩互相撞击然后选择一个有比较锋锐棱角的作为砍树工具。
>
> > 故事得从一个论坛中的帖子说起:
>
> > template<class T, class Init, class Release>
> > struct MyRAII{
> > T resource;
> > Init f;
> > Release g;
>
> > MyRAII(typename Init::param x){ resource = f(x); }
> > ~MyRAII() { g(resource); }
> > };
>
> > 这是Xinyu LIU兄在帖子中给出的如何使用RAII的例子,我说其中有风险,LIU兄推荐我看看Scott Mayers的Effective
> > C++的第二章,手头上暂时没有书就没有看了,不过我仍然坚持我的观点。
>
> > 且听我一一道来。
>
> > 虽然我不知道第二章究竟说了些啥,但是我想应该是所谓的对象构造过程中抛出异常的话,那么其析构不会被调用......云云。
>
> > 很多人看到这儿总是有一股子恶心在胃里蔓延,为啥?C++的规矩太多了,你看你看......,这个。但是且慢,这个很正常,这个规矩非常容易理解,因为析构说到底是一个存放对象销毁时要做一些影响环境的事情的地方。而如果对象构造过程中抛出异常,导致对象没有被完整的构造出来,那么,不完整的甚至不合法的对象怎么被析构?所以这个规矩既必要又合乎逻辑。
>
> > 有人不满了,面向对象的语言多了,没见过哪个语言有这个要求的。
>
> > 我从两方面回答这个诘问。
>
> > 一、大多数拥有GC的OO语言,其实没有析构函数的容身之地的,一个对象死了就死了,由运行时销毁,不需要程序员在里面指示对象死的时候再怎么临死反击,回光返照一下。实际上,在有GC支持的OO语言中,都会有一个很明确的禁令:不要再析构的地方复活一个对象(任意一个,不一定是自己)。为什么?因为对象的依赖图是非常复杂的,GC时,意味着已经分析清楚对象依赖存活图了,死掉的就要被回收了,结果如果又重新改变了对象存活图,这次GC的准备要全部废弃,不能进行任何回收,需要重新分析和整理,然后才能回收。设想这样一种情景:好几个调皮的对象都在自己析构的时候修改了对象存活图,然后在第一GC时第一个对象导致了重新开始,第二次GC时第二个对象有导致重新开始......,第n+1次时第一个对象又导致了重新开始,啊,世世代代无穷匮也。
>
> > 二、C++在构造对象这件事情上的视角更低,考虑的更精致,这就导致了C++的对象构造不是一个整体的步骤,而是一系列的步骤组成的。任何一个环节出了问题,都会导致构造失败,而这时候未完成对象的状态对于程序员来说是未知的(这就是为什么不应该调用析构的原因,析构函数总是假设对象完整合法,但是这儿不是的,所以不能调用),但是运行时当然知道是怎么回事了。根据对称要求,没有构造出来的对象就不应该被析构。
>
> > 那么为什么我仍要说LIU兄的实现有危险呢?因为,唉,又是C++的规矩,C++的麻烦似乎就在这儿,无穷无尽的规矩,C++说:对象在构造函数执行到函数体的{时,构造完成。
>
> > 啊,这是什么意思?怎么这么BT,这简直就是不可理喻啊。
>
> > 但是还是且慢,我们仍有理由。C++设计的时候有一些指导原则,其中有一个非常重要的就是零负荷(也就是说,不需要为没有用到的功能付出代价),零负荷原则投射到类型系统上的结果就是:C++之父需要"原始权利"。他想让我们设计的类型跟int
> > float char之类的原始类型一样而无二致。就是因为这个诱因和想法,导致了上面说的那个不太可以理喻的规矩。
>
> > 如果你在玩这个游戏,请你遵守游戏规则,否则会有伤害加身。除非你不玩这个游戏。
>
> > C++这个游戏需要遵守的规则太多了,我们必须深刻到理解为什么有这些规则,才能不被它伤害。
>
> > 我们开始推导过程
>
> > 先热个身,注意,这儿有装逼倾向,自视清高者不要看。
>
> > int i;
> > int i();
> > int i(0);
> > int i = 0;
> > int i = int();
> > int i = int(0);
>
> > 它们有什么差异?如果把int泛化为类型T,可能我们会更直观一些。
>
> > 这里面引入的概念包括:构造、初始化。其实,初始化并不是构造的一部分,很多人还会进一步联想到赋值这个跟初始化很类似的概念,当然,这更不是构造的一部分了。那么构造究竟干了什么呢?
>
> > 上面的作为全局或者栈上对象跟堆上对象的关于创建的唯一差别就是没有了内存分配过程。
>
> > 关于new operator干的事情(也就是创建对象的标准流程),我们都耳熟能详了。首先调用operator
> > new分配内存,然后调用构造函数把这个生鲜的内存活化为一个对象。
>
> > 构造函数有很多工作要做,逐层的构造对象(由根而父而己......),每个构造对象过程包含了准备一些实现OO特性的基础设施(比如vtbl和vptr的准备等),按照特定顺序逐一构造数据成员(这儿也有可能需要程序员参与,一会儿详细说),然后是用户进行的一些初始化操作。
>
> > 实际上,对于int这样的类型,其对象的构造过程简单的难以置信----完全没有。由于不是类型体系上的一个节点,所以不会有父类型对象先被构造;由于是一个实类型,不需要OO特征,所以不需要准备基础设施,也由于没有数据成员,不需要做任何事情,所以其构造就是空。
>
> > 我们再从C++构造函数的语法上看一些端倪。
>
> > class B {
> > public:
> > B(int b) {x = b;};
> > private:
> > int x;
> > };
>
> > class C : public B {
> > public:
> > C() : B(0), i(0), j(k) {k = 5;}
> > private:
> > int const i;
> > int& j;
> > int k;
> > };
>
> > 很多人认为C++的语法奇特,尤其是其构造函数,简直就是集恶劣语法之大成。这么说不无道理。上面的代码我尽量写得中规中矩,没有多继承,没有让人崩溃的顺序问题的引入,但是仍然让人感觉嗔目结舌的难以理解。注意,上面的构造函数的初始化列表是不可避免的,我没有为了展现这个语法而故意用初始化语法。恰恰相反,能不用的我都没有用,甚至因此背上了性能代价也在所不惜。
>
> > 上面就展现了那些需要程序员参与构造的成员变量。这儿牵扯到一个比较复杂的问题,C++某些对象要求构造和初始化必须同时完成。
>
> > 这是可以理解的......,......
>
> > 很多同学就不满意了,有什么不是可以理解的呢?难道别的OO语言都是虚拟的存在,就C++才真实存在?别的OO语言那个有这个复杂精致的过了分的语法呢?这里面牵扯到视角的问题。C++的语法希望尽可能的把低层次的实现细节都表达出来,而别的OO语言的语法希望尽可能的吧低层次的实现细节都隐藏起来。故有此差异。至于这两个谁有谁劣,人人心里有一杆秤吧。
>
> ...
>
> read more >>
不知道你想说明啥。至少上面的“C++说”是错误的。我想你把什么搞混了吧。
见下面的测试代码:
#include <iostream>
#include <stdexcept>
using namespace std;
class A {
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
private:
A(const A&);
A& operator=(const A&);
};
class B {
public:
B();
~B();
private:
A a_;
};
B::B()
{
cout << "B()" << endl;
throw runtime_error("Exception from B");
}
B::~B()
{
cout << "~B()" << endl;
}
int main()
{
try {
B* p = new B();
} catch (exception& e) {
cout << "Exception caught: " << e.what() << endl;
}
}
运行结果:
A()
B()
~A()
Exception caught: Exception from B
在执行到B()的{时,A已经构造完成。在执行到B的}之前,B没有构造完成。所以异常发生时,A会被析构;但B因为还没构造完成,不会调用~B()。
如果把你那句话换成这样,就可以了:成员对象在容器对象的构造函数执行到函数体的{时,构造完成。
但这是你的意思吗?字面差异也太大了吧。
我用的是GCC 4.0/4.2/4.5,刚才还在CodePad上试过:
2011/9/11 up duan <fix...@gmail.com>:
即使在全局对象的析构函数里,编译器很多仍然能保证你试用cout的安全性。比如,MSVC有不同优先级的初始化段:
http://support.microsoft.com/kb/104248
你的正常代码根本不会有任何问题。虽说,在这种情况下,为保险起见,我也是使用printf。一般情况则无此必要。
2011/9/11 up duan <fix...@gmail.com>:
2011/9/11 Yongwei Wu <wuyo...@gmail.com>:
混合语言编程不是问题,因为异常本来就该是C++语言内部使用的。你会想让Java的异常抛到C++里去吗?动态库也类似,这是一个设计问题。至于析构时崩溃就更扯了,任何C++的书都会告诉你析构时不允许抛任何异常。
如果你说C++没有STL和Boost也挺好,我只能说,这不是C++社区的主流观念。建议你考虑Objective-C,简单,没有异常,目前流行程度也不错,只是结果程序多半只能跑在苹果的平台上。
2011/9/11 yuan zhu <zy49...@gmail.com>:
>>>>>>> 虽然我不知道第二章究竟说了些啥,但是我想应该是所谓的对象构造过程中抛出异常的话,那么其析构不会被调用......云云。
>>>>>>>
>>>>>>>
>>>>>>> 很多人看到这儿总是有一股子恶心在胃里蔓延,为啥?C++的规矩太多了,你看你看......,这个。但是且慢,这个很正常,这个规矩非常容易理解,因为析构说到底是一个存放对象销毁时要做一些影响环境的事情的地方。而如果对象构造过程中抛出异常,导致对象没有被完整的构造出来,那么,不完整的甚至不合法的对象怎么被析构?所以这个规矩既必要又合乎逻辑。
>>>>>>>
>>>>>>> 有人不满了,面向对象的语言多了,没见过哪个语言有这个要求的。
>>>>>>>
>>>>>>> 我从两方面回答这个诘问。
>>>>>>>
>>>>>>>
>>>>>>> 一、大多数拥有GC的OO语言,其实没有析构函数的容身之地的,一个对象死了就死了,由运行时销毁,不需要程序员在里面指示对象死的时候再怎么临死反击,回光返照一下。实际上,在有GC支持的OO语言中,都会有一个很明确的禁令:不要再析构的地方复活一个对象(任意一个,不一定是自己)。为什么?因为对象的依赖图是非常复杂的,GC时,意味着已经分析清楚对象依赖存活图了,死掉的就要被回收了,结果如果又重新改变了对象存活图,这次GC的准备要全部废弃,不能进行任何回收,需要重新分析和整理,然后才能回收。设想这样一种情景:好几个调皮的对象都在自己析构的时候修改了对象存活图,然后在第一GC时第一个对象导致了重新开始,第二次GC时第二个对象有导致重新开始......,第n+1次时第一个对象又导致了重新开始,啊,世世代代无穷匮也。
>>>>>>>
>>>>>>>
>>>>>>> 二、C++在构造对象这件事情上的视角更低,考虑的更精致,这就导致了C++的对象构造不是一个整体的步骤,而是一系列的步骤组成的。任何一个环节出了问题,都会导致构造失败,而这时候未完成对象的状态对于程序员来说是未知的(这就是为什么不应该调用析构的原因,析构函数总是假设对象完整合法,但是这儿不是的,所以不能调用),但是运行时当然知道是怎么回事了。根据对称要求,没有构造出来的对象就不应该被析构。
>>>>>>>
>>>>>>>
>>>>>>> 那么为什么我仍要说LIU兄的实现有危险呢?因为,唉,又是C++的规矩,C++的麻烦似乎就在这儿,无穷无尽的规矩,C++说:对象在构造函数执行到函数体的{时,构造完成。
>>>>>>>
>>>>>>> 啊,这是什么意思?怎么这么BT,这简直就是不可理喻啊。
>>>>>>>
>>>>>>>
>>>>>>> 但是还是且慢,我们仍有理由。C++设计的时候有一些指导原则,其中有一个非常重要的就是零负荷(也就是说,不需要为没有用到的功能付出代价),零负荷原则投射到类型系统上的结果就是:C++之父需要"原始权利"。他想让我们设计的类型跟int
>>>>>>> float char之类的原始类型一样而无二致。就是因为这个诱因和想法,导致了上面说的那个不太可以理喻的规矩。
>>>>>>>
>>>>>>> 如果你在玩这个游戏,请你遵守游戏规则,否则会有伤害加身。除非你不玩这个游戏。
>>>>>>>
>>>>>>> C++这个游戏需要遵守的规则太多了,我们必须深刻到理解为什么有这些规则,才能不被它伤害。
>>>>>>>
>>>>>>> 我们开始推导过程
>>>>>>>
>>>>>>> 先热个身,注意,这儿有装逼倾向,自视清高者不要看。
>>>>>>>
>>>>>>> int i;
>>>>>>> int i();
>>>>>>> int i(0);
>>>>>>> int i = 0;
>>>>>>> int i = int();
>>>>>>> int i = int(0);
>>>>>>>
>>>>>>> 它们有什么差异?如果把int泛化为类型T,可能我们会更直观一些。
>>>>>>>
>>>>>>>
>>>>>>> 这里面引入的概念包括:构造、初始化。其实,初始化并不是构造的一部分,很多人还会进一步联想到赋值这个跟初始化很类似的概念,当然,这更不是构造的一部分了。那么构造究竟干了什么呢?
>>>>>>>
>>>>>>> 上面的作为全局或者栈上对象跟堆上对象的关于创建的唯一差别就是没有了内存分配过程。
>>>>>>>
>>>>>>> 关于new operator干的事情(也就是创建对象的标准流程),我们都耳熟能详了。首先调用operator
>>>>>>> new分配内存,然后调用构造函数把这个生鲜的内存活化为一个对象。
>>>>>>>
>>>>>>>
>>>>>>> 构造函数有很多工作要做,逐层的构造对象(由根而父而己......),每个构造对象过程包含了准备一些实现OO特性的基础设施(比如vtbl和vptr的准备等),按照特定顺序逐一构造数据成员(这儿也有可能需要程序员参与,一会儿详细说),然后是用户进行的一些初始化操作。
>>>>>>>
>>>>>>>
>>>>>>> 实际上,对于int这样的类型,其对象的构造过程简单的难以置信----完全没有。由于不是类型体系上的一个节点,所以不会有父类型对象先被构造;由于是一个实类型,不需要OO特征,所以不需要准备基础设施,也由于没有数据成员,不需要做任何事情,所以其构造就是空。
>>>>>>>
>>>>>>> 我们再从C++构造函数的语法上看一些端倪。
>>>>>>>
>>>>>>> class B {
>>>>>>> public:
>>>>>>> B(int b) {x = b;};
>>>>>>> private:
>>>>>>> int x;
>>>>>>> };
>>>>>>>
>>>>>>> class C : public B {
>>>>>>> public:
>>>>>>> C() : B(0), i(0), j(k) {k = 5;}
>>>>>>> private:
>>>>>>> int const i;
>>>>>>> int& j;
>>>>>>> int k;
>>>>>>> };
>>>>>>>
>>>>>>>
>>>>>>> 很多人认为C++的语法奇特,尤其是其构造函数,简直就是集恶劣语法之大成。这么说不无道理。上面的代码我尽量写得中规中矩,没有多继承,没有让人崩溃的顺序问题的引入,但是仍然让人感觉嗔目结舌的难以理解。注意,上面的构造函数的初始化列表是不可避免的,我没有为了展现这个语法而故意用初始化语法。恰恰相反,能不用的我都没有用,甚至因此背上了性能代价也在所不惜。
>>>>>>>
>>>>>>> 上面就展现了那些需要程序员参与构造的成员变量。这儿牵扯到一个比较复杂的问题,C++某些对象要求构造和初始化必须同时完成。
>>>>>>> 这是可以理解的......,......
>>>>>>>
>>>>>>>
>>>>>>> 很多同学就不满意了,有什么不是可以理解的呢?难道别的OO语言都是虚拟的存在,就C++才真实存在?别的OO语言那个有这个复杂精致的过了分的语法呢?这里面牵扯到视角的问题。C++的语法希望尽可能的把低层次的实现细节都表达出来,而别的OO语言的语法希望尽可能的吧低层次的实现细节都隐藏起来。故有此差异。至于这两个谁有谁劣,人人心里有一杆秤吧。
>>>>>>>
>>>>>>>
>>>>>>> 在我心里的秤是这样的----为了叙述方便,我们不C++的角度叫做A,别的角度叫做B----A的学习成本太高了,而且一般说来,没有太好的回报,因为经过这么多年的软件实践,已经知道了很多"最佳"方法,而B就是那种可以直接支持"最佳"方法而且不会引入学习成本的方式。但是如果已经在A上投入了大量的学习成本,已经学会了,那么A的优势是,它不像是给你一个万能梯子让你爬上去,而是给了你一套定制梯子的方法让你可以针对特定的情形定制特定的梯子,然后爬上去。当然,所谓B的万能梯子,只是在大多数情况下有效,在某些极限的情况下会失去效用,而逻辑上A的方式可以沿用无穷。
>>>>>>>
>>>>>>>
>>>>>>> 如果要在各个计算机语言间进行对比,甚或要设计一门新的语言(我说的是通用目的的,专用目的的不在此讨论范围内),那么C++一定要参照,其中大量的经验和规矩以及无穷无尽的细节甚至错误都是需要借鉴的宝库。
>>>>>>>
>>>>>>> 扯远了,回来......
>>>>>>>
>>>>>>>
>>>>>>> 上面的C++构造的语法【初始化列表】是为了应付两个看似简单的扩展,一个用处很大:const,用来规定一个对象在当前视角下是状态不变的,另一个----引用----主要用于IO中(此IO是指把function作为一个系统,其参数和返回值视作出入接口而言的),用于避免copy构造的调用,其最初的本意其实就是给一个对象一个新名字。
>>>>>>> 明眼人能看出来,采用异常的方案是不可运行的,只是伪代码。原因很简单。它依赖的那些基础设施并不满足采用这个方案的基本要求。比如:fopen打开失败后不是返回一个NULL的FILE指针,而是抛出一个FileOpenException,并且,FILE本身不是一个普通的结构,而是一个符合RAII要求的类。等等......,这都是麻烦,这都不是真的,这意味着:基于异常和RAII的方案对环境的假设跟现实社会中的大量存在的代码基的现状并不匹配。
>>>>>>>
>>>>>>> 最后,我还想说说Xinyu LIU兄那个MyRAII类我认为的潜在的问题,只是我一家之言,供参考。
>>>>>>> 可商榷之一:委托MyRAII进行管理的Resource,必须同时把自己的Init和Release给出。
>>>>>>>
>>>>>>> 可商榷之二:MyRAII对Init的要求太强,不像是一个可以通用的库,而且由于Resource的构建参数都是经由MyRAII的构造传进去的,这导致耦合性大增而灵活可重用性大减。
>>>>>>>
>>>>>>>
>>>>>>> 其实RAII虽说直译为资源获取即初始化,但后面那个没有说出来的部分才是RAII提供的精髓的价值。这其实就是智能指针干的事。如果这样定位的话,我们同时也发觉LIU兄没有采用Scott
>>>>>>> mayers击节赞叹的boost智能指针销毁持有物而不增加模板参数的技巧,我是觉得那个技巧非常好。
>>>>>>>
>>>>>>>
>>>>>>> 一旦思路转换到这儿,我们就能发现,只要用好智能指针,RAII实现起来麻烦程度就降低了不少。不过还有一些让人难受的细节作祟,而据说魔鬼在细节里藏身。
>>>>>>>
>>>>>>> 比如:
>>>>>>> struct FileClose {
>>>>>>> operator()(FILE* file) {
>>>>>>> fclose(file);
>>>>>>> }
>>>>>>> };
>>>>>>>
>>>>>>> shared_ptr<FILE> file(fopen(filename, "rb"), FileClose());
>>>>>>> 看起来不错啊。唯一的瑕疵是FileClose的存在,不过有了lambda,这会解决的。只可惜......
这里 A 是 B 的成员,B::B() 在函数体内抛异常,A 对象的构造是在初始化列表里完成的,
当进入 B::B() 函数体时,A 已经是一个完整的对象,因此 B::B() 抛异常的时候会被析构。
还可以试试
1. B 是 A 的派生类,B::B() 在函数体内抛异常,会引起 A 被析构
2. B 有 A1 和 A2 两个成员,依序排列,A2::A2() 抛异常,会引起 A1 被析构;附加题:如何 catch 这个异常,比较它发
生在函数体之外?catch 住了能干什么?能不能继续构造 B ?
3. A 和 B 是基类,C 多重继承 A 和 B,依次顺序,B::B() 或 C::C() 其中一个抛异常,A 会被析构。
4. A 是基类,B C 虚拟继承 A,D 多重虚拟继承 B 和 C,然后 A::A() 或 B::B() 或 C::C() 其中一个抛异常,
看看哪个会被析构。
5. A a[5]; 在构造第3个元素时抛异常,前两个 A 会被析构。
6. 往函数传两个 A 对象 foo(A a1, A a2),要构造两个参数对象,如果后构造的那个 A::A() 抛异常,会导致已经构造好的
A 参数对象被析构。
7. 以 const reference 方式往函数传两个 A 对象 foo(const A& a1, const A& a2),然后
foo(1, 2.0) 会构造两个临时对象(假设 A 有合适的构造函数),如果后构造的那个 A::A() 抛异常,会导致已经构造好的 A 临时
对象被析构。
C++ 的异常比其他语言复杂得多,至少其他语言没有这么多自动析构的规则要记住。我估计还没有列完,只是凭记忆列了一些。
On Sep 11, 7:18 pm, up duan <fixo...@gmail.com> wrote:
> 给出这样的实例时,请给出编译器型号和版本。
>
> 2011/9/10 Yongwei Wu <wuyong...@gmail.com>
>
>
>
>
>
>
>
> > 2011/9/9 up duan <fixo...@gmail.com>:
不过,对于某些论点,我确实看得心头火起。有些东西,是可以简单测试一下就可以得到结论的。我做了20多年的C++开发,都会在发表我的论点前常常测试一下;所以我很不明白,为什么有人发表观点那么轻率。
讨论技术问题用花哨的文辞一般也不足取,你会更在意你的感觉而不是观点。讨论技术问题,需要的是就事论事。
回到Google对C++异常支持的问题,拿Xinyu说过的话,这多半是一个政治问题。坦率地说,我认为这是Google在这个问题上没有远见的表现。这可能也直接导致了Android对于C++的支持比起iOS差远了。
2011/9/12 jiang yu <yu.jia...@gmail.com>:
另外,在iOS开发中,我可以混合C++和Objective-C的代码,非常爽:
std::ostringstream oss;
oss << ...;
NSString *result = [NSString stringWithUTF8String:oss.str().c_str()];
换句话说,我可以直接在Objective-C代码中使用C++的接口。在Android中,C++异常都只是近来才支持的,不要说JNI的麻烦事了。
2011/9/12 jiang yu <yu.jia...@gmail.com>:
> 好吧,我承认对没有XXX,就没有XXX,就只能XXX的言论过敏了,就好像是没有XXX,就没有新中国一样。there always be another
> way.
> C没有异常,靠个返回值,这么多年不一样好好的;C++是匹野马,像Google那样,对C++做裁剪,在工程上来讲是务实的态度。同样,我再吹毛求疵一下,这恐怕推出不这句话:
> <坦率地说,我认为这是Google在这个问题上没有远见的表现。这可能直接导致了Android对于C++的支持比起iOS差远了。
>
> 在 2011年9月12日 上午11:02,Yongwei Wu <wuyo...@gmail.com>写道:
>>
>> 呵呵,你想引诱我说一些会导致我被ban的话吗?:-)
>>
>>
>> 不过,对于某些论点,我确实看得心头火起。有些东西,是可以简单测试一下就可以得到结论的。我做了20多年的C++开发,都会在发表我的论点前常常测试一下;所以我很不明白,为什么有人发表观点那么轻率。
>>
>> 讨论技术问题用花哨的文辞一般也不足取,你会更在意你的感觉而不是观点。讨论技术问题,需要的是就事论事。
>>
>>
>> 回到Google对C++异常支持的问题,拿Xinyu说过的话,这多半是一个政治问题。坦率地说,我认为这是Google在这个问题上没有远见的表现。这可能也直接导致了Android对于C++的支持比起iOS差远了。
>>
>> 2011/9/12 jiang yu <yu.jia...@gmail.com>:
>> > My God, 不知wuyongwei现在什么状态,暴走了吗。
>> >
>> > 在 2011年9月11日 下午9:59,Yongwei Wu <wuyo...@gmail.com>写道:
>> >>
>> >>
>> >>
>> >> 没有异常,你就只能利用函数的返回值来表示错误,也就意味着你几乎不可能让一个函数返回一个对象,也就意味着STL和Boost变得几乎不可能。也就是说,C++就根本不是今天的C++了。
>> >>
>> >>
>> >>
>> >> 混合语言编程不是问题,因为异常本来就该是C++语言内部使用的。你会想让Java的异常抛到C++里去吗?动态库也类似,这是一个设计问题。至于析构时崩溃就更扯了,任何C++的书都会告诉你析构时不允许抛任何异常。
>> >>
>> >>
>> >>
>> >> 如果你说C++没有STL和Boost也挺好,我只能说,这不是C++社区的主流观念。建议你考虑Objective-C,简单,没有异常,目前流行程度也不错,只是结果程序多半只能跑在苹果的平台上。
--
已经说过了,异常不应该穿越语言边界。几乎所有此类代码可以看作是设计错误。我们的项目中,一般对外只暴露C的接口,错误接口必须良好定义,异常也是不允许跑到模块外的。
2011/9/12 yuan zhu <zy49...@gmail.com>:
vector<Object> v(100);
还有很多情况,比如复制构造和赋值。
Java和Objective-C没这样的问题,因为所有的对象都是堆上构造,按指针(引用)传递。在允许按值传递的C++里,不用异常,泛型基本无解。
2011/9/12 qiaojie <qia...@gmail.com>:
2011/9/12 jinhu wang <wangji...@gmail.com>:
2011/9/12 Zhangming Niu <niuzha...@gmail.com>:
“不把C++特性都用上就感觉不爽”,有吗?:-)
事实上,很少有人能掌握所有的C++特性。该用的时候还是应该用。活到老,学到老。异常是90年代中期加入的,我还没见过一位C++大师说异常不该加入:Stroustrup,
Alexandrescu, Meyers, Koenig, Sutter, ……
2011/9/12 qiaojie <qia...@gmail.com>:
> 混合编程的异常处理,需要在调用边界的地方将异常转换成错误返回值,这个属于基本常识了。同样STL、Boost之类的库没有异常就不能用了,这种情况也比较罕见。
> 我对C++异常的看法保持中立态度,异常使用得当会使代码变得干净简洁,但也不是非用不可,在实际工程中对C++特性做一些裁剪也是无可厚非的。怕就怕的是由于对异常一知半解、错误使用出了问题,然后就把异常贬低的一文不值;或者是C++的死忠,不把C++特性都用上就感觉不爽,追求语言特性甚于项目本身,这些都不可取。
--
结论部分写道:
There is no simple answer to the "exceptions or error codes" question.
The decision needs to be made based on a specific situation that a
development team faces. Some rough guidelines may be:
* If you have a good development process and code standards that are
actually being followed, if you are writing modern-style C++ code
that relies on RAII to clean up resources for you, if your code base
is modular, using exceptions may be a good idea.
* If you are working with code that was not written with exception
safety in mind, if you feel there is a lack of discipline in your
development team, or if you are developing hard real-time systems,
you should probably not use exceptions.
2011/9/12 qiaojie <qia...@gmail.com>:
> 如果明确不用异常的话,可以把new的行为定义成no throw,当然也不允许让
> vector里放的对象抛异常,另外可以在编译器里关掉异常,STL的实现里也会
> 区分有异常和没异常两种情况分别做不同处理。
>
> 2011/9/12 Yongwei Wu <wuyo...@gmail.com>
>>
>>
>> 连最普通的vector,都是依赖于异常的。内存不足时会抛bad_alloc;另外,
>> 由于vector里放的对象可能会抛异常,唯一可能的通用处理方法就是使用异
>> 常。
>>
>> 已经说过了,异常不应该穿越语言边界。几乎所有此类代码可以看作是设计
>> 错误。我们的项目中,一般对外只暴露C的接口,错误接口必须良好定义,异
>> 常也是不允许跑到模块外的。
>>
>> 2011/9/12 yuan zhu <zy49...@gmail.com>:
>> > 高度依赖异常的stl 组件,一般只怕很少有人用哟。
>> > boost里面使用异常的往往也是在那些自定义的泛型cast 中才用吧,如boost::any 与boost::variant
>> > 的boost::any_cast和boost::get之流的,(自定义转换强制要求了返回值类型)。其他常用组件应该很少使用异常吧。
>> >
>> > 另外混合语言编程里面,兄弟试过c++异常穿越obj-c栈传播的结果吗,啧啧。错误反馈回来的dump文件,不仅实际错误几乎完全远离现场,且传播异常的时候又导致obj-c的一些代码错误(
>> > 因为obj-c不捕捉c++异常,眼睁睁的看着c++异常路过),郁闷死人了。
>> > 推荐兄弟开发不要光顾着爽。开发是个团队活,谨慎点好,不要用一些高级特性让自己爽了,让同事们郁闷。尤其是混合语言编程。
--
2011/9/12 qiaojie <qia...@gmail.com>:
try {
// Do something
SomeWindowsFunction(Utf8ToWideString(my_string));
// Do other things
} catch (ConversionError& e) {
// Error processing
}
如果不使用异常:
// Do something
wstring temp_wstr;
if (!ConvertUtf8ToWideString(my_string, temp_wstr)) {
// Error processing
}
SomeWindowsFunction(temp_wstr);
// Do other things
上面的对象还是使用异常了,因为string可能会抛异常。一般还是可以工作的,
但更严格的代码应当类似这样:
// Do something
size_t buffer_len = strlen(my_string) + 1;
wchar_t* buffer = (wchar_t*)alloca(buffer_len * sizeof(wchar_t));
if (!ConvertUtf8ToWideString(my_string, buffer, buffer_len)) {
// Error processing
}
SomeWindowsFunction(buffer);
// Do other things
在转换较多的情况下,使用异常的优势是非常明显的。否则,大量错误判断代码
会非常严重地干扰正常的代码阅读。
除了使用临时对象的情况,容器和模板也依赖于异常。除非你能够让所有的人使
用你的二段式初始化的标准,改造或者不使用STL,并且在代码中不使用复制构
造和赋值表达式。
2011/9/13 bronco <renfe...@gmail.com>:
--
你的要求挑战的原话是:"我肯定有不合理的地方,才会导致在构造的时候要抛
出异常"。就请问一下,我的Utf8ToWideString,构造时遇到非法UTF-8序列抛
异常,有什么不合理的地方?
我这儿没有表达出来的另外一点是,我在输入由自己决定的时候,在大部分函数
里可以完全不处理这个异常,只在较外层的地方catch一下,当前的操作失败。
使用返回值,你得在函数的每一层里进行处理。麻烦,易出错。
除了以上的一般异常好处外,再强调一遍,要使用C++的函数按值返回对象、临
时对象、泛型编程,异常基本不可或缺。你当然可以选择不用这些特性,但说别
人使用这些特性"不合理"则是另外一回事了。
2011/9/13 bronco <renfe...@gmail.com>:
这个世界的设计如果都想你想的那样,永远不变化,瀑布式开发,那当然就爽得很了。
里面唯一不够严格的是没有处理可能的bad_alloc。不过,在iPhone环境里,程
序在收到bad_alloc之前似乎就已经被OS杀掉了,所以,也不怎么必要。
我可不用瀑布式开发。在非瀑布式的开发环境里,模块间的接口仍然是需要清楚
定义的(变化需要实现者和使用者之间进行协调)。不定义好,肯定会导致混乱
的编程结果。
2011/9/15 yuan zhu <zy49...@gmail.com>:
Hi,
我再次重申乔姆斯基(Chomsky)的观点:语言可以影响人的思维,举个例子:
- 日语的结构是: 主语+宾语+谓语
我们可以看到这多大程度影响了日本人的思维(请切勿联想发挥到其他层面,不然这个thread就危险了)。
PS: 如果按照被引用来排名,MIT数据显示,乔姆斯基是科学界影响最大的人。对人类影响最大的的top 10中,唯一活着的人。
另外的9位包括柏拉图,亚里士多德,西塞罗,黑格尔,莎士比亚等等,绝大多数是哲学家。
你有不回复的自由:-)。希望继续讨论的话,请就事论事。
> 自己都知道代码里随时可能有std::bad_alloc这样的稍微不注意就会忽略掉的
> 因子,还说C++异常不会穿过Objective-C栈;
iPhone里内存不足的话,程序会被系统杀掉,catch也没什么用。在我的情况
里,那段是核心代码(关键任务在C++里完成),即使能catch住,也做不了什么
事情了。
> 然后前面又说stl依赖这些异常,这些异常很重要,c++不能没有异常不然stl
> 工作不下去了;
当然,不然你写一个不依赖于异常的STL出来试试!
可以想象,下面的语句都是非法的:
string s1("Hello");
string s2 = s1;
s1 = "World";
s2 += ' ';
s2 += s1;
你确认,你想要的是这样的代码:
string s1;
if (!s1.init("Hello"))
goto Bad_alloc_exit;
string s2;
if (!s2.init(s1))
goto Bad_alloc_exit;
...
> 然后再说这些异常咱们可以不当回事,直接扔给系统管吧,
仅限于iPhone下bad_alloc的情况,是这样。
> set_new_handler都省去了......
这本来就不是必需的。缺省行为是bad_alloc,我不觉得有什么问题。
> 看到这么洒脱的言论,我真为我项目组里的人通宵分析用户反馈的dump感觉不
> 值。另外我又一次见到了stringstream这样的大神代码,实在值得做个标记留
> 念一下。
为什么不能用stringstream呢?你推荐用什么?希望不是s*printf吧。
> 另外怎么老有人拿做库的方法和做app的方法混淆呢?
如果你是指我写的"模块间的接口仍然是需要清楚定义的",我不认为有任何混
淆的地方。你们的应用程序里也应该分模块、有清晰的模块界限吧。
我可没说不处理是非法的。我说的是如果C++没有异常的话,合法的使用STL的代
码会长成那个样子。
> 到底要不要处理bad_alloc?bad_alloc是不是可以关掉?
拿句套话:It depends。在不同的应用场景下,处理各不相同。
> STL是不是可以不依赖bad_alloc?能不能给个准话?
作为一个通用的模板库,我认为不可以。
--
On Sep 24, 6:09 pm, jinhu wang <wangjinhu...@gmail.com> wrote:
> 请教大家两个问题:
>
> 1. 一般用异常做什么?
> 2. 内存失败抛出异常,有多少同学真正在程序里捕获到过内存失败异常。
>
> 在 2011年9月19日 下午10:22,Yongwei Wu <wuyong...@gmail.com>写道:
>
>
>
>
>
>
>
> > 2011/9/19 qiaojie <qiao...@gmail.com>:
> > > 好奇怪啊,前面说bad_alloc不用处理,后面又说不处理bad_alloc是非法的。
>
> > 我可没说不处理是非法的。我说的是如果C++没有异常的话,合法的使用STL的代
> > 码会长成那个样子。
>
> > > 到底要不要处理bad_alloc?bad_alloc是不是可以关掉?
>
> > 拿句套话:It depends。在不同的应用场景下,处理各不相同。
>
> > > STL是不是可以不依赖bad_alloc?能不能给个准话?
>
> > 作为一个通用的模板库,我认为不可以。
>
> > > 2011/9/19 Yongwei Wu <wuyong...@gmail.com>
>
> > >> 2011/9/17 yuan zhu <zy498...@gmail.com>:
> > >> > 2011/9/16 Yongwei Wu <wuyong...@gmail.com>
>
> > >> >> 那是我的例子。我可没有让C++异常穿过Objective-C。那段代码里,对oss的操
> > >> >> 作全部是C++的代码,从C++的对象里得到结果,只在最后,把结果转成
> > >> >> Objective-C的字符串,传回到Objective-C的代码里去。C++的异常都在C++的代
> > >> >> 码里处理完了。
>
> > >> >> 里面唯一不够严格的是没有处理可能的bad_alloc。不过,在iPhone环境里,程
> > >> >> 序在收到bad_alloc之前似乎就已经被OS杀掉了,所以,也不怎么必要。
>
> > >> >> 我可不用瀑布式开发。在非瀑布式的开发环境里,模块间的接口仍然是需要清楚
> > >> >> 定义的(变化需要实现者和使用者之间进行协调)。不定义好,肯定会导致混乱
> > >> >> 的编程结果。
>
> > >> >> 2011/9/15 yuan zhu <zy498...@gmail.com>:
>
> > 兄弟我说的是上文那个objective-c混合c++编程那种,和你说的分模块调接口那种玩意,完全是2回事。你去看那个人的stringstream的例子 吧。
>
> > >> >> > 这个世界的设计如果都想你想的那样,永远不变化,瀑布式开发,那当然就爽得很了。
>
> > >> >> > On 9/12/11, Yongwei Wu <wuyong...@gmail.com> wrote:
>
> > 连最普通的vector,都是依赖于异常的。内存不足时会抛bad_alloc;另外,由于vector里放的对象可能会抛异常,唯一可能的通用处理方法就是使 用异常。
>
> > 已经说过了,异常不应该穿越语言边界。几乎所有此类代码可以看作是设计错误。我们的项目中,一般对外只暴露C的接口,错误接口必须良好定义,异常也是不允许跑到 模块外的。
>
> > >> >> >> 2011/9/12 yuan zhu <zy498...@gmail.com>:
我想这个有很多资料可以查吧。
随便说两句。一般用异常描述不容易发生、正常流程以外的错误。带来的主要好处是我可以在好几层以上的函数里统一处理,不用层层检查。
另外,如前面讨论过,如果要让构造函数、某些操作符返回错误,异常是唯一的方式。
> 内存失败抛出异常,有多少同学真正在程序里捕获到过内存失败异常。
正常程序里不多。但仍需考虑到:
* 我见过物理内存充足的情况下内存分配有暂时的失败(Windows)
* 有些内存分配操作是用户(包括文件、网络等)输入引起的,程序需要判断内存分配是否失败,以免用户构造攻击
* 内存不足时,仍应当尽量保存用户数据
--
Cheers,
Oliver Yang
Twitter: http://twitter.com/yangoliver
Blog: http://blog.csdn.net/yayong
--------------------------------------------------------------------
An OpenSolaris Developer
异常是语言层面的东西。从图灵机的角度来说,没什么是异常做得到,C做不到
的。从表达能力的角度,使用异常能表达一些以前程序员不愿意表达的东西。
就说我前几天举的string的例子。如果任何一次操作都要检查是否有错误,这样
的代码几乎是没法看的。所以传统上C程序员不会这样写代码,会使用固定的缓
冲区大小。C++程序员使用string的话,就会更愿意使用变长的缓冲区。如陈硕
所说,语言可以决定思维。
哪种好,我就不做辩论了。每个人都有自己的风格。C的风格可能可以更高效、
也更直截了当,但更容易导致不检查错误码和缓冲区溢出。“现代”C++的风格
可以更简洁(未必更容易写),异常不检查会导致程序退出,缓冲区溢出也较难
发生。
2011/9/26 jinhu wang <wangji...@gmail.com>:
Hi昨天无法回答你的问题,因为我去年跳槽到现在公司后,就加入这个项目,已经决定用SMP。今天查了一下相关文档,了解了原因.1. 因为AMP的核与核之间是完全独立的, 但是外部的resource并不太好分割,这样的话,不能简单的换板子就行(新的支持8core的板子替换原来的板子,原来板子跑的是PSOS),需要改硬件的架构.
2. 在AMP下,每个核在外部看来都是独立的系统.那也就是说,每个核都需要分别加载,初始化,配置. 这样对O&M系统影响比较大,这样也就不能简单的替换板子
3. 由于AMP把任务固定分在各个核里,这样不方便测量. 当把系统更新到更多核的板子上去时,AMP改动比较大,而在SMP上却几乎无需改动
4. SMP更普遍,并且更容易测试. 而且将来也有项目需要SMP,这样可以积累经验.