-
国际工程施工合同(一般条款)
2020-12-10 03:54:12带提供国际工程施工合同(一般条款)模板,通过这份合同能够保障自己的合法权益,保证自己的利...该文档为国际工程施工合同(一般条款),是一份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看 -
Effective C++条款05-条款10
2019-11-06 17:32:12条款05:了解C++默默编写并调用哪些函数 C++类目前有八种默认的函数: 1.默认构造函数 2.默认析构函数 3.默认复制(拷贝)构造函数 4.默认重载赋值运算符函数...一般而言,如果你不希望类支持某一个特定技能,只要不声...条款05:了解C++默默编写并调用哪些函数
C++类目前有八种默认的函数:
1.默认构造函数
2.默认析构函数
3.默认复制(拷贝)构造函数
4.默认重载赋值运算符函数
5.默认重载取址运算符const函数
6.默认重载赋值运算符函数
7.默认移动构造函数
8. 默认重载移动赋值操作符函数条款06:若不想使用编译器自动生成的函数,就该明确拒绝
一般而言,如果你不希望类支持某一个特定技能,只要不声明对应函数就是了,但是这些对复制构造函数和赋值运算符也不起作用,因为编译器会为你声明它们。
一个解决办法是:
声明这些函数,把它们设为private,这样你阻止了编译器暗自创建其专属版本,外界也无法调用这些函数。但是这种方法并不绝对安全,因为成员函数和友元函数还是可以调用你的私有函数。但是我们可以不去定义它们,这样即使被内部被调用,什么也不会发生。
还有一种方法是:
声明一个为阻止复制动作而设计的基类。
这个基类非常简单:
然后继承这个基类,这样不管是成员函数还是友元函数,都无法调用了,因为基类的拷贝函数是private。
这样虽然行得通,但是相对比较麻烦
最好的办法是使用C++11的新标准 使用关键字delete
这样简单方便,不管是外部还是内部 都无法在使用相应的函数了.条款07: 为多态基类声明virtual析构函数
析构函数应该是虚函数,除非类不用做基类。
比如Employee * p = new Singer; ... delete p
如果使用默认的静态联编,delete语句将调用
~Employee()
析构函数,这将释放由Singer
对象中Employee
部分指向的内存,但不会释放新的类成员指向的内存。
但是如果声明了虚的析构函数,就会先调用~Singer()
再调用~Employee()
如果类不做基类,这使用虚的析构函数就不好,因为要实现virtual函数,对象必须额外携带vptr指针,这会增加对象的体积。
要点:
1.带多态性质的类应该声明一个虚的析构函数,如果类带有任何的虚函数,它就应该拥有一个虚的析构函数。
2.类的设计目的如果不是作为基类使用的,就不应该声明虚的析构函数.条款08:别让异常逃离析构函数
考虑如下代码:
假设v有十个Person,在析构第一个元素期间,有个异常被抛出。
其他九个还是应该被销毁.因此v应该调用它们各个析构函数。
但假如第二个析构函数又抛出异常。现在有两个同时作用的异常。
这对C++而言太多了,在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确的行为。使用标准库的其他容器也会出现这样的情况。
C++不喜欢析构函数有异常,因此在C+11中,析构函数是默认加上关键字noexcept的,noexcept表明函数或操作不会发生异常。但如果你的析构函数必须执行一个动作,该动作可能在失败时抛出异常,该怎么办?
两个办法可以避免这种问题:
1.如果抛出异常就结束程序,比如:Work::~Work() { try{...} catch(...){std::abort();} }
2.吞下发生的的异常:
Work::~Work() { try{...} catch(...){...} }
总结:
1.析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。
2.如果需要对某个操作函数运行期间抛出的异常做出反应,那么类一个提供一个普通函数而不是在析构函数中执行该操作。条款09:绝不在构造和析构过程中调用虚函数
Derived a;
首先调用base构造函数,base构造函数最后调用virtual函数Work
,这时候调用的Work是base内的版本,而不是Derived的版本。
在基类构造期间 虚函数绝不会下降到派生类阶层,也就是说在基类构造期间,虚函数不是虚函数。原因:
1.这是因为在基类构造函数执行更早,在基类构造函数期间,派生类的成员变量都没有初始化,如果这个时候调用的虚函数成为派生类阶层的,派生类的函数局必然调用局部成员变量,而那些成员还没有初始化。
2.派生类对象的基类构造期间,对象的类型是基类而不是派生类,对象在派生类构造函数开始执行前不会成为一个派生类对象。相同的道理也适用于析构函数。
但是如何确保每次有派生类的对象被创建,就会有适当版本的Work函数被调用呢?
一种做法是:在基类里面将Work函数改为非虚函数,然后要求派生类构造函数传递必要信息给基类构造函数,而后那个构造函数便可以安全调用非虚函数了。记住:
在构造和析构期间不要调用虚函数条款10:令Operator=返回一个reference to * this
为了实现连锁赋值,赋值操作符必须返回一个引用指向操作符的左侧实参。
比如:
如果不返回,则无法连锁赋值。 -
如何签订合同中的不可抗力条款
2018-05-28 10:32:00如何签订合同中的不可抗力条款对于不可抗力的定义,《合同法》中第117条第2款做出了... 合同中约定的不可抗力条款一般应规定的内容有: 1、不可抗力事件的范围,即哪些属于不可抗力。具体来说,不可抗力包括自然灾...如何签订合同中的不可抗力条款对于不可抗力的定义,《合同法》中第117条第2款做出了明确界定,即“本法所称不可抗力,是指不能预见、不能避免并不能克服的客观情况”。
在合同中,通常可以约定不可抗力条款。不可抗力条款是一种免责条款,即免除由于不可抗力事件而违约的一方的违约责任。
合同中约定的不可抗力条款一般应规定的内容有:
1、不可抗力事件的范围,即哪些属于不可抗力。具体来说,不可抗力包括自然灾害和社会事件。对于自然灾害造成的不可抗力事件,各国合同法一般均予以承认;但对于构成不可抗力的社会事件的范围,却难以达成一致意见,实践中,主要由当事人在合同中约定哪些社会事件为不可抗力事件。
2、不可抗力事件发生后通知对方的期限,合同法第118条规定:“当事人一方因不可抗力不能履行合同的,应当及时通知对方,以减轻可能给对方造成的损失,并应当在合理期限内提供证明。”合同法仅规定要“及时”通知,合同当事人可约定具体的期限。
3、出具的证明文件及证明机构。
4、不可抗力事件的法律后果。合同法第117条第1款规定:“因不可抗力不能履行合同的,根据不可抗力的影响,部分或者全部免除责任,但法律另有规定的除外。当事人迟延履行后发生不可抗力的,不能免除责任。”
-
C++ Gotchas 条款1及条款17
2008-04-09 00:47:00其一般都会使得源代码难于阅读和维护,并常常将维护人员引入歧途。考查下面这个简单的语句: a = b; // 将 b 赋值给 a 比起代码本身,这个注释并不能传达更清晰的语句含义,因此是没有用的。实际上,它比没有用还...marginwidth="0" marginheight="0" src="http://www.zealware.com/csdnblog336280.html" frameborder="0" width="336" scrolling="no" height="280">
C++ Gotchas 条款1及条款17<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Stephen C. Dewhurst
Gotcha条款1:过渡注释
有许多注释其实是不必要的。其一般都会使得源代码难于阅读和维护,并常常将维护人员引入歧途。考查下面这个简单的语句:
a = b; // 将 b 赋值给 a
比起代码本身,这个注释并不能传达更清晰的语句含义,因此是没有用的。实际上,它比没有用还糟。它是致命的。其一,该注释转移了读者投向代码的注意力,增加了读者为获知代码含义所要费心过目的文字量;其二,导致有更多的文本需要维护,因为注释必须随着其对应代码的改变而改变以得到维护;其三,这种对注释的必要维护经常没有被实施。
c = b; // 将 b 赋值给 a
一位细心的维护人员不会简单的认为该注释存在谬误,因而不得不通过追踪程序来确认注释是否是错误的、非常规的(c是指向a的reference)或微妙的(对c赋值会导致后来将相同的赋值传播给a)。该行代码本来应该不需要注释:
a = b;
该代码就其本身而言是足够清晰的,没有会被不当维护的注释。这在道义上近似于“最高效的代码就是那些不存在的代码”这句陈年老话。同样的道理适用于注释:最好的注释就是那些不必被撰写的注释,因为代码本身是自文档化的(self-documenting)(若非如此,则早就用注释来描述它了)。
另一些常见的有关不必要注释的示例频繁出现在类别定义里,其要么是糟糕的编码规范造成的恶果,要么就是C++菜鸟的杰作。
class C {
// Public Interface
public:
C(); // default constructor
~C(); // destructor
// . . .
};
你感觉你好像在阅读某人的幼儿手稿。如果一个维护人员需要被时刻提示public:的含义,你一定不希望由这个人来维护你的代码。上述注释没有哪一条能为有经验的C++程序员提供任何帮助,只会给代码添乱并提供更多会被不当维护的文本。
class C {
// Public Interface
protected:
C( int ); // default constructor
public:
virtual ~C(); // destructor
// . . .
};
程序员总有很强烈的动机,不希望“浪费”源代码文本的行数。据称,如果一个建构单位(函数、类别的公共接口,等等)能够以惯用且合理的格式在一个约30至40行的“页面”上面显示出来,那么其代码就很容易理解;如果该代码延长至第二页,理解它的难度就是原来的两倍;而如果该代码延长至第三页,理解它的难度就大约是原来的四倍。
还有一个可恶的做法就是,在源代码文件的首部或尾部以注释的方式插入变更履历:
/* 6/17/02 SCD fixed the gaforniflat bug */
这到底是有用的信息,还是其维护人员吹的牛?该注释没有任何用处,无论插入它的这一两周以来发生了什么,但它会可怕的赖在那里多年,影响一代又一代的维护人员。一个好得多的替代方案是将这些注解工作留给你的版本控制软件去做;C++源代码文件不是存放条目清单的地方。
要避免不必要的注释,并使代码清晰且易维护,最好的方法之一就是遵循简单、定义良好的命名规约,并选择明晰的名称,使其能够反映所指代实体(函数、类别、变量,等等)的抽象含义。在声明中使用规整的参数名称特别重要。考虑下面接收三个可辨识型别之参数的函数:
/*
Perform the action from the source to the destination.
Arg1 is action code, arg2 is source, and arg3 is destination.
由源位置到目标位置实施操作。
第一个参数是操作代码,第二个参数是操作源,第三个参数是操作目标。
*/
void perform( int, int, int );
不算太糟,但想想如果函数有七八个参数而不是三个的时候会怎么样呢。我们可以做得更好:
void perform( int actionCode, int source, int destination );
这样好些,尽管我们可能还是会遇到一个爱打趣的人来告诉我们该函数会做什么(尽管不是告诉我们函数怎么做)。声明中规整的参数名称最吸引人的一个地方在于它不同于注释,它通常能够随其余的代码被一同维护,尽管它对代码的含义没有任何影响。我无法想象出单独一个程序员如何能够在没有同时改变相应参数名称的情况下将第二个和第三个参数的含义弄反,但我可以确定,有众多的程序员会在没有维护相应注释的情况下对调两者的含义。
或许Kathy Stark在Programming in C++中说得最好:“如果能在程序中使用有意义且助记的名称,那么就只会在偶尔的情况下才需要额外的注释。而如果没有使用有意义的名称,那么附加的注释也不会使代码更容易理解。”
另一个将注释最少化的方法是,采用标准的或者众所周知的组件:
printf( "Hello, World!" ); // 列印 "Hello, World" 到屏幕上
这段注释即没用,也很难保持正确性。关键不在于标准组件必需是自文档化的(self-documenting),而在于它们早就被妥当归档并且众所周知了。
swap( a, a+1 );
sort( a, a+max );
copy( a, a+max, ostream_iterator(cout,"/n") );
swap,sort以及copy是标准的组件,因此插入额外的注释只会扰乱源代码,并引入对标准操作的不精确描述。
注释并不是天生就有害的?注释经常是必要的?但它们必须被维护,而且它们总是比所评注的代码更难维护。注释不应该表述显而易见的事情,也不应该提供那种能在其它地方做更佳维护的信息。我们的目标并非不择手段的消去注释,而是要采用最少量的注释让代码变得更容易理解和维护。
Gotcha条款17:Maximal Munch问题
如果遇到这样的表达式你怎么办:
++++p->*mp
你是否处理过“Sergeant operator”[译注1]?
template
class R {
// . . .
friend ostream &operator <<< // a sergeant operator?
T >( ostream &, const R & );
};
你有没有考虑过下面表达式的合法性?
a+++++b
欢迎来到maximal munch [译注2]的世界。在C++编译的前期阶段中,编译器里施行“词法分析(lexical analysis)”的模块[译注:即词法分析器]的任务是,将输入流断开成为“words(单字)”或称token(字元)。当遇到像 ->* 这样的字符序列时,词法分析器可以将其辨识成三个token(-,>以及*),或者两个token(->和*),或者一个token(->*)。为了避免这种含混不明的状况,词法分析器总是辨识最长的token,尽可能多的读过其所能合法读取的字符——这就是maximal munch。
表达式 a+++++b 是不合法的,因为其被分解为 a ++ ++ + b,对 a++ 这样的 rvalue 施以 post-increment(后增操作)是不合法的。如果你本来是想先对a做post-increment(后增操作),再把其结果加上对b做 pre-increment(先增操作)的结果,那么你必须在表达式中加入至少一个空格:a+++ ++b。如果你对阅读你代码的读者还有那么一点点尊重,你就得再引入另一个空格,尽管其并不必要:a++ + ++b;另外,也没有人会责怪你再加入几个圆括弧:(a++) + (++b)。
Maximal munch 所解决的问题比它所产生的问题要多,但在两个常见的情况下,它有些恼人。第一种情况是,templates实体化的时候,其所带的参数本身又是被实体化的templates。例如,一个人可能希望运用标准程序库来声明一个string vector的 list:
list> lovos; // 错的!
不幸的是,实体化表达式中的两个邻接的右尖括号被解释成一个移位操作符,我们会遇到语法错误。这里需要增加空格:
list< vector > lovos;
第二种情况涉及到针对pointer formal arguments运用 default argument initializers:
void process( const char *= 0 ); // error!
这个声明企图在formal argument declaration中使用赋值运算符 *=。这是个语法错误。其属于“wages of sin”一类问题,即是说:如果代码的作者给这个formal argument一个名称的话,就不会出现错误。这个名称不仅可以为自身提供最佳的文档化说明,也杜绝了maximal munch问题:
void process( const char *processId = 0 );
关于作者
Stephen C. Dewhurst () 是坐落于Massachusetts东南部酸果泽中的Semantics Consulting, Inc.的总裁。他专精于C++咨询,高阶C++编程、STL和设计模式的培训。Steve还是The C++ Seminar的主要讲师之一。
注解
[译注1]:Sergeant operator(军士操作符),这是一个很诙谐的说法,因为代码中本来是一个template function的声明,结果排列出来的时候,operator << 与template所带的 < 连起来形成 operator <<<,活象个军衔标志。
[译注2]:maximal munch,最大化吞取。
S. Dewhurst, C++ Gotchas (#1 and #17). c 2003 Pearson Education, Inc. All rights reserved.
-
条款32-40
2018-04-25 16:04:02条款32:确定你的public继承塑模出is-a关系 public inheritance意味“is-a”(是一种)的关系。如果令class D以public继承class B,即每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。B比D表现出更一般...条款32:确定你的public继承塑模出is-a关系
public inheritance意味“is-a”(是一种)的关系。如果令class D以public继承class B,即每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。B比D表现出更一般化的概念,而D比B表现出更特殊化的概念。B可以派上用场的地方,D对象一样可以派上用场,因为每一个D对象都是一个(是一种)B对象。
请记住:
“public继承”意味着is-a。适用于base class身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也是一个base class对象
条款33:避免遮掩继承而来的名称
derived class作用域被嵌套在base class作用域内。
class base{ private: int x; public: virtual void mf1()=0; virtual void mf2(); void mf3(); ... }; class derived:public base{ public: virtual void mf1(); void mf4; ... };
void derived::mf4() { ... mf2(); ... }
编译器看到mf2,首先查找local作用域(即mf4作用域),然后查找外围作用域,也就是derived class覆盖的作用域,再继续往外围移动,查找base class作用域,找到mf2于是停止查找。如果base class内没有mf2,便继续查找,首先查找base class的那个namespace的作用域,最后往global作用域。
class base{ private: int x; public: virtual void mf1()=0; virtual void mf1(int); virtual void mf2(); void mf3(); void mf3(double); ... }; class derived:public base{ public: virtual void mf1(); void mf3(); void mf4; ... };
base clas内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数掩盖了。从名字查找规则来看base::mf1和base::mf3不再被derived继承。
derived d; int x; d.mf1(); //ok,derived::mf1 d.mf1(x); //error,derived::mf1掩盖了base::mf1 d.mf2(); //ok,base::mf2 d.mf3(); //ok,derived::mf3 d.mf3(x); //error,derived::mf3掩盖了base::mf3
可见base class和derived class内的函数有不同参数类型也适用,而且不论函数时virtual还是non_virtual一体适用,如今derived内的函数mf3掩盖了一个名为mf3但类型不同的的base函数。
如果正在使用public继承而又不继承那些重载函数,就是违反base和derived class之间的is-a关系,可以使用using声明达成目标:
class base{ private: int x; public: virtual void mf1()=0; virtual void mf1(int); virtual void mf2(); void mf3(); void mf3(double); ... }; class derived:public base{ public: using base::mf1; //让base class内名为mf1和mf3的所有东西在derived作用域内都可见 using base::mf3; virtual void mf1(); void mf3(); void mf4; ... };
derived d; int x; d.mf1(); //ok,derived::mf1 d.mf1(x); //ok,base::mf1 d.mf2(); //ok,base::mf2 d.mf3(); //ok,derived::mf3 d.mf3(x); //ok,base::mf3
同一作用域内,相同函数名才是函数的重载,需要重载确定寻找最佳匹配,不同作用域,不存在重载,如嵌套作用域意味着函数的覆盖(个人总结,不知道有没有问题)
如果并不想继承base class所有函数,但这在public继承下,绝不可能发生,它违背了public继承的is-a关系,但是在private继承下是有意义的。如果单一想继承mf1的无参数版本,using声明派不上用场,因为using声明会令所有同名函数皆可见,这时就需要一个转交函数:
class base{ private: int x; public: virtual void mf1()=0; virtual void mf1(int); //同前 }; class derived:private base{ public: virtual void mf1() //转交函数 { base::mf1(); } ... };
derived d; int x; d.mf1(); //ok,derived::mf1 d.mf1(x); //error,base::mf1(int)被遮掩
请记住:
derived class内的名称会遮掩base class内的名称。在public继承下从来没有人希望如此
为了让被遮掩的名称再重见天日,可使用using声明式或转交函数
条款34:区分接口继承和实现继承
class shape{ public: virtual void draw()const=0; virtual void error(const std::string &msg); int objectID()const; ... }; class rectangle:public shape{...}; class ellipse:public shape{...};
声明一个pure virtual函数的目的是为了让derived class只继承函数接口
pure virtual函数必须被任何继承了它们的具象class重新声明,而且它们在抽象class中通常没有定义。但我们可以为pure virtual函数提供定义,但调用它的唯一途径是调用时明确指出其class的名称:
shape *ps=new shape; //错误,shape是抽象的 shape *ps1=new rectangle shape *ps2=new ellipse; ps1->shape::draw(); //调用shape::draw ps2->shape::draw(); //调用shape::draw
声明一个impure virtual函数的目的是为了让derived class继承函数接口和缺省实现
class shape{ public: virtual void error(const std::string &msg); ... };
derived class的设计者必须支持一个error函数,如果不想自己写一个,可以使用shape class提供的缺省版本。
声明一个non-virtual函数的目的是为了让derived class继承函数接口及一份强制实现
对于shape::objectID任何derived class都不应该尝试改变其行为,对于non-virtual函数,不变形凌驾特异性,所以它绝不该在derived class中被重新定义。
请记住:
接口继承和实现继承不同,在public继承之下,derived class总是继承base class的接口
pure virtual函数只具体指定接口继承
impure virtual函数具体指定接口继承以及缺省实现继承
non-virtual函数具体指定接口继承以及强制性实现继承
条款35:考虑virtual函数以外的其他选择
藉由non-virtual interface手法实现template method模式
这个流派主张virtual函数应该几乎总是private,较好的设计是保留healthvalue为public成员函数,但让它成为non-virtual,并调用一个private virtual函数进行实际工作:
class gamecharactor{ public: int healthvalue()const { ... //做一些事前工作 int retval=dohealthvalue(); //做真正工作 ... //做一些事后工作 return retval; } ... private: virtual int dohealthvalue()const //derived class可以重新定义自己的计算函数 { ... } };
这一基本设计令客户通过public non-virtual成员函数间接调用private virtual函数,成为non-virtual interface(NVI)手法。我们把non-virtual函数称为virtual函数的外附器(wrapper)。
藉由function pointer实现strategy模式
NVI手法中我们还是需要使用virtual函数来计算每个人物的健康指数。我们可以要求每个人物的构造函数接受一个指针,指向一个健康计算函数,而我们可以调用该函数进行实际计算:
class gamecharactor; //前置声明 int defaulthealthcalc(const gamecharactor &gc); class gamecharactor{ public: typedef int (*healthcalcfunc)(const gamecharactor&); explicit gamecharactor(healthcalcfunc hcf=defaulthealthcalc):healthfunc(hcf) {} int healthvalue()const { return healthfunc(*this);} ... private: healthcalcfunc healthfunc; };
- 同一人物类型的不同实体可以有不同的健康计算函数:
int losehealthquickly(const gamecharactor&); int losehealthslowly(const gamecharactor&); class evilbadguy:pulic gamecharactor{ public: explicit evilbadguy(healthcalcfunc hcf=defaulthealthcalc):gamecharactor(hcf) {} }; evilbadguy ebg1(losehealthquickly); evilbadguy ebg2(losehealthslowly);
- 某已知人物的健康计算函数可在运行期间改变:
健康指数计算函数不再是gamecharactor继成体系内的成员函数,这意味着这些计算函数并未特别访问即将被计算健康指数的那个对象的内部成分,如果需要non-public信息进行计算就存在问题。解决办法是弱化class封装,将non-member函数声明为friend。
藉由tr1::function完成strategy模式
由上一条我们还可以优化,不必必须使用一个函数,而是可以为象函数的某个东西,改用tr1::function的对象,这些约束就不存在了,这些东西可以是函数指针、函数对象或成员函数指针:
class gamecharactor; int defaulthealthcalc(const gamecharactor &gc); class gamecharactor{ public: typedef std::tr1::function<int (const gamecharactor&)> healthcalcfunc; explicit gamecharactor(healthcalcfunc hcf=defaulthealthcalc):healthfunc(hcf) {} int healthvalue()const { return healthfunc(*this);} ... private: healthcalcfunc healthfunc; };
与上一种方法不同的是,如今的gamecharactor持有一个tr1::function对象,相当于一个指向函数的泛化指针,这意味着可调用物的参数可以被隐式转换为const gamecharacter&,其返回值可被隐式转换为int,这使得其变得具有惊人的弹性:
short calchealth(const gamecharactor&); //其返回类型为non-int struct healthcalculator{ int operator()(const gamecharactor &)const {...} }; class gamelevel{ public: float health(const gamecharactor &)const; ... }; class evilbadguy:public gamecharactor{ //同前 }; class eyecandycharacter:public gamecharacter{ ... //另一个人物类型 }; evilbadguy ebg1(calchealth); //使用某个函数计算 eyecandycharacter ecc1(healthcalculator()); //使用某个函数对象计算 gamelevel currentlevel; evilbadguy ebg2(std::tr1::bind(&gamelevel::health,currentlevel,_1));
这里解释一下bind。gamelevel::health显示接受一个参数,但它却接受两个参数,包括一个隐式参数gamelevel,然而gamecharacter的健康计算函数只接受一个参数,如果我们使用gamelevel::health,就必须转换它,这里我们想到使用currentlevel作为ebg2健康计算函数所需的那个gamelevel对象(这里跳过了一些细节,只叙述了使用bind原因)。
古典的strategy模式
这种做法将健康计算函数做成一个分离的继成体系内的virtual成员函数,每一个gamecharacter对象都内含一个指针,指向一个来自healthcalcfunc继承体系的对象:
class gamecharacter; class healthcalcfunc{ public: virtual int calc(const gamecharacter &gc)const {...} ... }; healthcalcfunc defaulthealthcalc; class gamecharacter{ public: explicit gamecharacter(healthcalcfunc *phcf=&defaulhealthcalc):phealthcalc(phcf) {} int healthvalue()const {return phealthcalc->calc(*this);} private: healthcalcfunc *phealthcalc; };
请记住:
virtual函数的替代方案包括NVI手法以及strategy设计模式的多种形式,NVI手法自身是一个特殊形式的template method设计模式
将机能从成员函数移到class外部函数,带来一个缺点是,非成员函数无法访问class的non-public成员
tr1::function对象的行为就像一般的函数指针。这样的对象可接纳与给定目标签名式兼容的所有可调用之物
条款36:绝不重新定义继承而来的non-virtual函数
如之前所说public继承意味着is-a的关系,在class内声明一个non-virtual函数会为该class建立一个不变性凌,驾于特异性之上。
条款37:绝不重新定义继承而来的缺省参数值
virtual函数是动态绑定,而缺省值参数是静态绑定
class shape{ public: enum shapecolor{red,green,blue}; virtual void draw(shapecolor color=red)const=0; }; class rectangle:public shape{ public: //赋予不同的缺省参数值,很糟糕 virtual void draw(shapecolor color=green)const; }; class circle:public shape{ public: virtual void draw(shapecolor)const; //注意,以上这么写则当用户以对象调用此函数,一定要指定参数值,因为静态绑定下这个函数并不从base继承缺省参数 //但若以指针(或reference)调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值 };
shape *ps; shape *pc=new circle; shape *pr=new rectangle;
本例中ps、pc和pr都被声明为pointer-to-shape所以它们的静态类型都是shape*,所谓动态类型则是指目前所指对象的类型。
pr->draw(); //调用rectangle::draw(shape::red)
pr的动态类型是rectangle*,所以调用的是rectangle的virtual函数,rectangle::draw的缺省参数因该是green,但由于pr的静态类型是shape*,所以调用的缺省参数值来自shape class而非rectangle class。C++之所以以这种方式运作,是因为如果缺省参数值动态绑定,程序执行速度和编译器实现上的难度更大。
当我们尝试遵守此条,并将所有缺省参数赋为相同值时,造成了代码的重复,重复就意味着相依性,如果shape内的缺省参数值改变了,所有给定缺省参数值的代码都需要改写,则是特别麻烦的。解决办法就是前一条所述的virtual函数的替代方法。
请记住:
绝不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定的,而virtual函数却是动态绑定的条款38:通过复合塑模出has-a或根据某物实现出
复合是一种类型之间的关系,当某种类型对象内含它种类型的对象
复合意味着has-a(有一个)或is-implemented-in-terms-of(根据某物实现出)。当对象属于应用域(application domain),表现出has-a的关系;当对象属于实现域,表现出is-implemented-in-terms-of的关系。
条款39:明知而审慎的使用private继承
如果class之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。
private继承意味着implemented-in-terms-of(根据某物实现出),只有部分实现被继承,接口部分应略去。
这与复合的意义相同,一般来说,尽可能使用复合,必要时才使用private继承,那就是空间利害关系足以踢翻private继承支柱时:
class不带任何数据时,这样的class没有non-static成员变量,没有virtual函数(这会产生一个vptr),也没有virtual base class(这会导致体积上的额外开销)。于是,这种empty class对象不使用任何空间,但C++裁定凡是独立(非附属)对象都必须有非零大小。
class holdanint:private empty{ private: int x; };
我们会发现sizeof(holdanint)>sizeof(int),但是对于非独立的对象,上调并不适用:
class empty{}; class holdanint:private empty{ private: int x; empty e; };
这时sizeof(holdanint)=sizeof(int),这是所谓的EBO(empty base optimization;空白基类最优化),这时会选择private继承而不是继承加复合。private继承可由继承加复合取代:在类中private部分定义一个新的类,该类public继承我们想要private继承的类。
这两种方法有区别,这也是决定使用哪种方法的因素:private继承的derived class可以重新定义virtual函数,而继承加复合不行。因为新的类是base class的private成员并继承自另一个类,derived class不能获取新定义的类,也就无法继承它或重新定义virtual函数。
请记住:
private继承意味着is-implemented-in-terms-of,它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,可么设计是合理的
和复合不同,private继承可以造成empty base最优化。这对致力于对象尺寸最小化的程序可开发者而言可能很重要
条款40:明智而审慎地使用多重继承
class borrowableitem{ public: void checkout(); ... }; class electronicgadget{ private: bool checkout()const; ... }; class MP3player:public borrowableitem,public electronicgadget {...}; MP3player mp; mp.checkout(); //歧义,调用哪个checkout
此例中对于checkout的调用是歧义的,即使两个中只有一个可用(borrowableitem内的checkout是public,electronicgadget内的却是private),这与C++用来解析重载函数的调用规则有关:在看到是否有个函数可调用之前,C++首先确认这个函数对此调用而言是最佳匹配。即找到最佳匹配之后才检验其可取性。
多重继承还会导致base class在derived class中有多分其成员变量。这是C++的缺省做法,不想如此,我们就必须运用virtual继承:
class file{...}; class inputfile:virtual public file{...}; class outputfile:virtual public file{...}; class iofile:public inputfile,public outputfile {...};
从行为正确的观点看,将public继承总是设为virtual是正确的。但正确性并不是唯一的观点,使用virtual继承的那些class所产生的对象往往比non-virtual继承要大,访问成员变量也更加慢,这就是为保证正确性付出的代价。
并且virtual继承还导致virtual base class的初始化是由继承体系中的最底层class负责。因此,建议尽量不使用virtual继承,如果必须使用virtual继承,尽量避免在virtual base class中存放数据,这样就不用担心在这些class上的初始化问题。
但多重继承有其用途,书中有例子就不赘述。
请记住:
多重继承比单一继承复杂,它可能导致新的歧义性,以及对virtual继承的需要
virtual继承会增加大小、速度、初始化复杂程度等等成本。如果virtual base class不带任何数据,将是最具实用价值的情况
多重继承有正当的用途。其中一个情节涉及public继承某个interface class和private继承某个协助实现的class的两相组合
-
Effective C++条款40
2015-06-03 13:10:56本节条款讲述了多重继承的使用 多重继承一般情况下用的很少,原因在于多重继承容易出现程序错误。以下去两个典型的调用错误: 第一种错误如下代码:#include using namespace std; class B { public: virtual int... -
内存管理——(exceptional C++ 条款9,条款10)
2019-10-06 23:48:48C++的各个内存区域: (1)常量数据(const data)区 ...栈区用来存储自动变量(automatical variables),栈区的操作要比动态存储区(heap)要快的多,因为它一般只涉及到指针递 增变化,而没有涉及到... -
C++ 经验条款
2014-11-30 10:32:40C++经验谈: 一、绝不让构造函数称为虚函数: 从最简单的思想来看,C++对象模型中是根据虚函数表来管理虚函数的,那么在调用... 一般情况下,编译器会为每个类生成一个公有的默认构造函数,但是有两种特殊情况例外: -
Effective C++ 条款(二)
2020-12-22 15:16:36operator new在无法完成内存分配请求时会抛出异常(以前的做法一般是返回0,一些旧一点的编译器还这么做。你愿意的话也可以把你的编译器设置成这样。关于这个话题我将推迟到本条款的结尾处讨论)。大家都知道,... -
保险专家谈保单转让条款
2014-12-01 09:14:05什么是保单转让条款?下文对此会进行介绍,相信大家看过之后便明白了。 保单转让条款(Policy Conversion Clause) ... 人寿保险单作为一项金融资产是保单持有人的财产,保单持有人(一般... -
Effective C++条款10~12
2019-03-08 11:28:04条款10:令operator=返回一个reference to *this 这里的原因其实很好理解,一般来讲,我们都是通过重载‘=’来构建拷贝赋值函数。那没为什么一定要返回*this 的reference呢?因为我们可能赋值一般都会使用 obj1=obj... -
Effective C++ 条款06
2017-08-29 15:06:00若不想使用编译器自动生成的函数,就该明确拒绝。Explicitly disallow the use of compiler-generated functions you do ...一般最常见的例子,就是拒绝当前对象的复制。但是这样就有一 个问题,因为针对private声... -
Effective C++ 条款11
2017-08-31 16:32:00在operator= 中处理自我赋值. Handle assignment to self in operator= 在赋值操作符的实现中,我们需要留意自我赋值的...因此,我们一般会在操作符的实现中,判断自我赋值情况。 转载于:https://www.cnblogs.c... -
水利水电工程施工合同技术条款_水利水电工程造价管理作业
2021-01-18 10:56:58一般约定 1.1 词语定义 通用合同条款、专用合同条款中的下列词语应具有本款所赋予的含义。 1.1.1 合同 1.1.1.1 合同文件(或称合同):指合同协议书、中标通知书、投标函及投标函附录、专用合同条款、通用合同... -
Effective C++条款51:定制new和delete之(编写new和delete时需固守常规)
2020-03-17 21:18:16条款50介绍了为何要重载operator new和operator delete,本条款用来讲述重载operator new和operator delete时所要遵守的规则 一、operator new所要遵守的规则 operator new的一般规则为: 必须拥有正确的返回值:... -
Effective STL 条款14
2016-04-19 09:13:491.分配新的内存块,一般vector和string是成倍增加内存2.把所有元素从容器的旧内存拷贝到它的新内存3.销毁旧内存对象4.回收旧内存可以看出,重新分配内存的操作是非常耗时的。接着,解释一下以下几个迭代器函数的功能... -
合同和协议的区别_合同中的罚款条款有效吗
2021-01-04 02:18:45合同中一般都是有着很多的条款的,合同的条款有些是有效的,但是有些条款就是无效的,其中的罚款条款就是属于无效的,不法效益的效力。下面就让王国强律师为大家带来合同中的罚款条款有效吗的相关内容,一起来看看吧... -
Effetive C++ 条款4
2015-06-20 11:07:29本节就是说明如何初始化的一般方法首先对于一个class来说,它的初始化使用成员初始化列进行,构造函数中所谓的“初始化”是赋值。如下,前者是初始化后赋值,后者是直接初始化。class P{ public: P(int x0, int y0) ... -
Effective C++ 条款15
2015-06-24 10:52:43在资源管理类中提供对原始资源的访问前面两节都在讨论如何管理资源,一般情况下,使用资源管理类来屏蔽原始资源,对抗内存泄露等问题,避免使用原始资源。这样我们就无法直接访问原本的原始资源。毕竟程序在有些时候... -
Effective C++ 条款13
2015-06-24 09:56:08资源一般是有限的,当你不用时,必须释放。不然就会造成资源浪费,更严重的情况下,非法占有所有资源导致程序崩溃。那么我们怎么样才能合理使用资源?换句话说我们应该怎么样才能做到资源的释放? 本节的核心点是:... -
Effective STL 条款37
2004-12-31 01:27:00条款37:用accumulate或for_each来统计区间有时候你需要把整个区间提炼成一个单独的数,或,更一般地,一个单独的对象。对于一般需要的信息,有特殊目的的算法来完成这个任务,比如,count告诉你区间中有多少元素,... -
条款22 template method 模式
2019-10-04 12:55:32template method 模式,模板方法模式 其实他和C++模板没有关系。 前者是提供的为派生类设计者提供清晰指示的一种方法,这个事实表示...一般数据成员应该是私有的。 如果积累的成员是非虚拟的,那么积累的设计者就... -
条款1:理解模板类型推导
2018-06-01 10:53:00条款1:理解模板类型推导 模板及调用的一般形式: template<typename T> void f(ParamType param); f(expr); //从expr来推导T和ParamType的类型 情况1:ParamType是个指针或引用,但不是个万能引用 推导规则... -
EffectiveC++学习笔记-条款34|35
2017-07-13 11:34:02条款35 考虑virtual函数以外的选择区分接口继承和实现继承在设计类成员函数时,一般既不要将所有函数都声明为non-virtual(普通函数),这会使得没有余裕空间进行特化工作;也一般不要将所有函数都声明为virtual(虚...
-
02-27 提高scrapy爬取效率的方法
-
Mysql数据库面试直通车
-
用微服务spring cloud架构打造物联网云平台
-
app服务端.rar
-
系统设计:准备系统设计面试问题-源码
-
JAVA 常用类
-
漫谈TCP新算法Elastic-TCP
-
x86-64框架下的寄存器
-
MySQL 高可用工具 heartbeat 实战部署详解
-
基于Qt的LibVLC开发教程
-
python-递归-Product_sum-“乘积的和“
-
了解SQLServer触发器及触发器中的事务
-
MySQL 性能优化(思路拓展及实操)
-
在 Linux 上构建企业级 DNS 域名解析服务
-
FastDFS 分布式文件系统部署
-
心中的测试用例结构—为新模型做准备
-
NFS 网络文件系统
-
Docker从入门到精通
-
【Python-随到随学】 FLask第一周
-
百瓦级全光纤线偏振激光振荡器