Effective C++摘要《第4章:类和函数:设计与声明》20090209

本文详细探讨了C++编程中的关键技巧与最佳实践,包括如何合理设计类接口、使用const特性、处理对象返回、避免数字与指针重载引发的问题等。通过具体的代码示例,帮助读者更好地理解和掌握这些原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

===条款18: 争取使类的接口完整并且最小===
===条款19: 分清成员函数,非成员函数和友元函数===
结论:假设f是想正确声明的函数,c是和它相关的类
·虚函数必须是成员函数。如果f必须是虚函数,就让它成为c的成员函数。
·operator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
·只有非成员函数对最左边的参数进行类型转换。如果f需要对最左边的参数进行类型转换,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
·其它情况下都声明为成员函数。如果以上情况都不是,让f成为c的成员函数。
===条款20: 避免public接口出现数据成员===
===条款21: 尽可能使用const===
const成员函数的目的当然是为了指明哪个成员函数可以在const对象上被调用
仅在const方面有不同的成员函数可以重载
class string {
public:
  ...
  // 用于非const对象的operator[] 注意返回引用
  char& operator[](int position)
  { return data[position]; }

  // 用于const对象的operator[]
  const char& operator[](int position) const
  { return data[position]; }

private:
  char *data;
};

string s1 = "hello";
cout << s1[0];                  // 调用非const string::operator[]
const string s2 = "world";
cout << s2[0];                  // 调用const string::operator[]

对非静态数据成员运用mutable时,const成员函数可以对mutable数据成员进行修改

===条款23: 必须返回一个对象时不要试图返回一个引用===
如果返回引用的话(为了提高性能)
1、第一个错误方法
inline const rational& operator*(const rational& lhs,
                                 const rational& rhs)
{
  rational result(lhs.n * rhs.n, lhs.d * rhs.d);
  return result;
}
上面不仅不能提高性能,反而存在一个严重错误:返回局部对象的引用
2、第二个错误方法
inline const rational& operator*(const rational& lhs,
                                 const rational& rhs)
{
  rational *result =
    new rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}
还是得负担调用构造函数的开销
还有一个问题是谁来负责delete掉new生成的对象==》内存泄漏
即使返回指针,在外面delete掉它(绝对不可能——条款31展示了这样的代码会是什么样的)也是非法的,如:
rational w, x, y, z;
w = x * y * z;
两个对operator*的调用都产生了没有名字的临时值,程序员无法看到,因而无法删除
3、第三个错误方法
inline const rational& operator*(const rational& lhs,
                                 const rational& rhs)
{
  static rational result;      // 将要作为引用返回的
                               // 静态对象
  lhs和rhs 相乘,结果放进result;
  return result;
}
这种方法,对于以下情况:
rational a, b, c, d;
if ((a * b) == (c * d)) { //永远为true,不管a,b,c和d是什么值
  //处理相等的情况;
}

if (operator==(operator*(a, b), operator*(c, d)))
因为operator*返回的是同一个内部的静态rational对象的引用
===条款24: 在函数重载和设定参数缺省值间慎重选择===
对很多函数来说,会找不到合适的缺省值,这种情况下就别无选择:必须重载函数
另一种必须使用重载函数的情况是:想完成一项特殊的任务,但算法取决于给定的输入值。这种情况对于构造函数很常见
===条款25: 避免对指针和数字类型重载===
问题:
void f(int x);
void f(string *ps);
f(0);                        // 调用f(int)还是f(string*)?  调用f(int)
如何用符号名(比如,null表示null指针)调用f(string*)?

方法一:
要想把void*指针传给某类型的指针,必须要有一个显式的类型转换。
void * const null = 0;             // 可能的null定义
f(0);                              // 还是调用f(int)
f(static_cast<string*>(null));     // 调用f(string*)
f(static_cast<string*>(0));        // 调用f(string*)
但f(null); // 错误! — 类型不匹配,2 个重载中没有一个可以转换所有参数类型,即两个函数都不能用

#define null 0l            // null现在是一个long int
void f(int x);
void f(string *p);
f(null);                   // 错误!——歧义 书上说这里有歧义,但我测试过了,没有歧义,会调用void f(int x)

方法二:
const                             // 这是一个const对象...
class {
public:
  template<class t>               // 可以转换任何类型
    operator t*() const           // 的null非成员指针
    { return 0; }                 //

  template<class c, class t>      // 可以转换任何类型
    operator t c::*() const       // 的null成员指针,这个是针对类的成员指针的
    { return 0; }
private:
  void operator&() const;         // 不能取其地址(见条款27)
} null;                           // 名字为null

f(null);                          // 将null转换为string*, 然后调用f(string*)

本条款要说明的问题:
以上所有那些产生正确工作的null的设计方案,只有在你自己是调用者的时候才有意义。
如果你是设计被调用函数的人,写这样一个给别人使用的null其实没有多大的用处,因为你不能强迫你的调用者去使用它。
例如,即使为你的用户提供了上面开发的那个null,你还是不能防止他们这样做:
f(0);                  // 还是调用f(int),
                       // 因为0还是int
所以,作为重载函数的设计者,归根结底最基本的一条是,只要有可能,就要避免对一个数字和一个指针类型重载。
===条款26: 当心潜在的二义性===
二义性举例
1、自定义转换
class B;                    // 对类B提前声明
class A {
public:
  A(const B&);              // 可以从B构造而来的类A
};
class B {
public:
  operator A() const;       // 可以从A转换而来的类B
};
void f(const A&);

B b;
f(b);                       // 错误!——二义
一种方法是调用类A的构造函数
另一种方法是调用类B里自定义的转换运算符,它将b转换成一个A的对象
2、C++语言的标准转换
void f(int);
void f(char);

double d = 6.02;
f(d);                         // 错误!——二义
解决办法
f(static_cast<int>(d));       // 正确, 调用f(int)
f(static_cast<char>(d));      // 正确, 调用f(char)
3、多继承
class Base1 {
public:
  int doIt();
};
class Base2 {
public:
  void doIt();
};

class Derived: public Base1,     // Derived没有声明
               public Base2 {    // 一个叫做doIt的函数
  ...

};

Derived d;
d.doIt();                   // 错误!——二义
解决办法
d.Base1::doIt();            // 正确, 调用Base1::doIt
d.Base2::doIt();            // 正确, 调用Base2::doIt
===条款27: 如果不想使用隐式生成的函数就要显式地禁止它===
如何禁止
声明成private并且不去定义它
template<class T>
class Array {
private:
  // 不要定义这个函数!
  Array& operator=(const Array& rhs);
  ...
};
现在,当用户试图对Array对象执行赋值操作时,编译器会不答应
===条款28: 划分全局名字空间===
namespace sdm {
  const double book_version = 2.0;
  class handle { ... };
  handle& gethandle();
}
用户于是可以通过三种方法来访问这一名字空间里的符号:
将名字空间中的所有符号全部引入到某一用户空间;using namespace sdm;
将部分符号引入到某一用户空间;using sdm::book_version;
通过修饰符显式地一次性使用某个符号;f(sdm::book_version);

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值