Effective C++(条款18-25)

条款18:让接口容易被正确使用,不容易被误用

你应该在你所有的接口中努力达成这些性质。

“促进正确使用”的办法包括接口一致性,以及与内置类型的行为兼容。

“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

tr1::shared_ptr支持定制型删除器(custom_deleter),这可以防范DLL问题。如下:

std::tr1::shared_ptr<Invstment>PInv(static_cast<Investment*>(0),custom_deleter);

tr1::shared_ptr有一个特别好的性质是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误,所谓的ceoss—DLL problem。这个问题发生于“对象在动态链接程序库DLL中 被new 创建,却在另一个DLL内被delete销毁”。tr1::shared_ptr可以被传递给任何其他的DLL,这个tr1::shared_ptr会追踪记录当所指对象的引用次数变为0时该调用的那个DLL's delete。




条款19:设计一个class就像设计type

几乎每一个class都要面临以下问题:

                 新type对象应该如何被创建和销毁:影响到class的构造函数和析构函数,以及内存分配函数和释放函数(new,delete)

                 对象的初始化和对象的赋值该有什么样的差别:决定构造函数和赋值操作符的行为之间的差异

                 什么是新type的“合法值”:决定成员函数必须进行的错误检查工作,也影响函数抛出的异常以及异常明细列

                 你的新type需要配合某个继承图系吗?:影响析构函数是否为virtual

                  新type需要什么转换:如果允许类型T1被转换为类型T2,就必须在class T1内写一个类型转换函数(如下);或者在class T2内写一个non-explicit-one-argument(可被单一实参调用)的构造函数;如果只允许explicit构造函数存在,就可以写出专门负责执行转换的函数,且不得为类型转化操作符或non-explicit-one-argument构造函数。

operator T2()const
 {return p1;}//p1是T1类型的

                                                       

                   什么样的操作符和函数对此type而言是合理的:决定将为class声明哪些函数,某些应该是member函数,某些则不是

                   什么样的标准函数应该驳回:确定声明哪些函数为private的

                   谁该取用新type的成员:决定成员是public/private/protected,哪个类或函数应该是friend,以及将他们嵌套于另一个之内是否合理

                   你的新type有多么一般化:如果你定义的是整个type家族,就应该定义一个新的class template

                   你真的需要一个新的type吗:决定是否只定义一个derived class以便为既有的class添加机能,这样是否更能达到目标




条款20:宁愿按引用传递(pass-by-reference-to-const)替换按值传递(pass-by-value)参数

缺省的情况下,C++以by value方式传递对象至(或来自)函数。注意:要计量以pass-by-reference-to-const方式替代pass-by-value,前者通常比较高效,并可以避免切割问题。参数声明为const是必要的,它保证不会改变传入的那个值。


如果窥视C++编译器的底层,你会发现,references往往以指针实现出来,因此pass-by-reference通常意味真正传递的是指针。但是,对于内置类型以及STL的迭代器和函数对象,pass-by-value往往效率高些。






条款21:必须返回对象时,别妄想返回其reference

绝不要返回指针或引用指向一个local stack对象,或返回reference指向一个heap-allocated对象(这种heap-based对象由new建立,何时进行delete对象是一个问题),或返回指针或引用指向一个local static对象而可能同时需要对个这样的对象(在多线程安全化方面有疑虑),可以使用函数返回的“指向static对象”的reference而不是使用static对象本身。

当然,当你“必须返回新对象”的时候,虽然可能要承受构造函数和析构函数的成本问题,但是,在返回引用和返回对象之间还是要挑出行为正确地那个。





条款22:将成员变量声明为private

将成员变量声明为private:可以赋予客户访问数据的一致性(使public里的每样东西都是函数,使其具有语法一致性)、

                                                可细微划分访问控制(使用函数可以让你对成员变量的处理有更精确的控制,因为许多成员变量都是需要被隐藏起来的)、

                                                允诺约束条件获得保证(封装可以方便日后对某个成员变量的值进行改变而class客户可以完全不知情)、

                                                并提供class作者以充分的实现弹性

几乎可以说,不封装意味着不可改变,成员变量的封装性与“成员变量的内容改变时所破坏的代码数量”成反比。protected成员变量(对使用它的所有继承类)就像public成员变量(对所有使用它的客户码)一样缺乏封装性。





条款23:宁以non-member、non-friend替换member函数

能够访问private成员变量的函数只有class的member函数和friend函数而已。如果你在一个member函数(它可以访问类的private数据,也可以取用private、enums、typedef等等)和一个non-member non-friend函数(它无法访问上述任何问题)之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是non-member non-friend函数,因为它并不增加“能够访问class内的private成分”的函数数量。


注意,上述论述只适用于non-member non-friend函数。因为friend函数对类private成员的访问权利和member函数相同。其次,让函数成为类的non-member并不意味这它不能成为另一个类的member函数


在c++中,比较自然的做法是让这个能实现跟一个类的member函数相同机能的non-member函数与该类处于一个命名空间(namespace)中。

namespace WebBrowerStuff{
     class WebBrower{...};
     void clearBrower(WebBrower& wb);
     ...
}

将所有便利函数放在多个头文件中,但隶属于同一个命名空间,用户可以轻松扩展便利函数,新的便利函数就像其他旧有的便利函数那样可用且整合为一体。


宁可拿non-member、non-friend替换member函数,这样做可以增加封装性、包裹弹性、机能扩充性




条款24:若所有参数都需要类型转换,请为此采用non-member函数

先看下面这个例子:

class Rational{
public:    
     ...
     const Rational operator*(const Rational& rhs)const;
};


Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result=oneHalf*2;//发生隐式类型转换
result=2*oneHalf;//错误

以对应的函数形式重写上述两个表达式,就会一目了然,

Rational result=oneHalf.operator*(2);
result=2.operator*(oneHalf);

只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。让operator*成为一个non-member函数,就允许编译器在每一个实参身上执行隐式类型转化。注意,member函数的反面是non-member函数,而不是friend函数,所以不能够只因为函数不该成为member 函数,就自动让它成为friend函数

class Rational{...};
    
const Rational operator*(const Rational& lhs,const Rational& rhs)const
{
      return Rational(lhs.num()*rhs.num(),lhs.denom()*rhs.denom());
};


Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result=oneHalf*2;//发生隐式类型转换
result=2*oneHalf;//发生隐式类型转换


条款25:考虑写出一个不抛出异常的swap函数

首先,如果swap的缺省实现码对你的class或class template提供可接受的效率,你不需要额外做任何事情。


其次,如果swap缺省版本效率不足,那么尝试做以下事情:

a.当std::swap对你的类型效率不高时,提供一个public swap成员函数,并确定这个函数不抛出异常,让他高效的置换你的类型的两个对象值(高效率的swap几乎总是基于对内置类型的操作,而内置类型的操作是绝不会抛出异常

b.如果你提供一个member swap,也该提供一个non-member swap用来调用前者,对于class(而非templates),也请特化std::swap

c.调用swap时应针对std::swap使用using声明,然后调用swap并且不带任何“命名空间资格修饰”

d.为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西


namespace WidgetStuff{  //假设Widget的所有相关机能都被置于WidgetStuff内
template<typename T>
class Widget
{
      public:
            Widget(const Widget& rhs);
            Widget& operator=(const Widget& rhs);
            void swap(Widget<T>& other)
            {
                    using std::swap;
                    swap(PImpl,other.PImpl);
            }
      private:
           WidgetImpl* PImpl;//此指针指向对象内含Widget数据
}

template<typename T>
void swap(Widget<T>&a,Widget<T>&b)
{
      a.swap(b);
}//non-member swap函数调用member swap版本

//调用swap函数:
template<typename T>
void dosomething(T&obj1,T&obj2)
{
      using std::swap;//令sta::swap在此函数内可用
      ...
      swap(obj1,obj2);//为T型对象调用最佳的swap版本
      ...
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值