C++设计与声明超详细讲解

让接口被正确使用不易被误用

除非有好的理由,否则应该让你的types的行为与内置types一致,请拿ints做范本

提供行为一致的接口!

阻止误用的办法包括建立新类型,限制类型上的操作,束缚对象值(比如要统计年月日,限制月的大小在1-12),消除客户的资源管理责任(智能指针)

shared_ptr支持定制型删除器,可被用来自动解除互斥锁等

宁以pass-by-reference-to-const替换pass-by-value

值传递要调用copy构造函数,释放时要调用多次析构函数(有副本),费时

const的必要性:引用传递代替值传递时,确保不会对传入的数据做改变

防止被切割:

class Window{
public:
 virtual void display();
}
class WindowWithScrollBars:public Window{
public:
 virtual void display();
}
void show(Window w){
 w.display();
}

当用一个WindowWithScrollBars对象传入show时,因为是值传递,会导致其特化信息被切割,变成了一个Window对象!无法多态了!

应该如下:传进来的窗口是什么类型,w就表现出哪种特性

void show(const Window& w){
 ...
}

说到底,引用是指针实现出来的,引用传递说到底也是传递的指针,如果有一个对象属于内置类型,值传递效率会比引用传递效率高一些。

值传递对于内置类型和STL的迭代器和函数对象来说代价不贵,其他类型还是选用const引用传递好!

必须返回对象时

别妄想返回reference

不是所有情况都是引用传递好

const A& operator*(const A& rhs){
 A result(rhs);//调用构造函数
 return result; 
}

返回了一个result的引用,但result是一个局部变量,离开作用域就被析构了!!!

引用不能返回一个局部对象,否则一败涂地

一个必须返回新对象的函数的做法是:就让他返回一个新对象呗

const A operator*(const A& rhs){
 A result(rhs);//调用构造函数
 return A(rhs); 
}

承受一个operator*构造和析构函数的代价即可

绝对不要返回一个指针或引用指向一个local stack对象(出作用域会被析构),或返回引用指向一个heap-allocated对象(无法保证合理的delete),或返回指针或引用指向一个local static对象而有可能同时需要多个这样的对象(一个指针修改了指向对象的参数后,其他指针指向的参数也被修改了)

将成员变量声明为private

语法一致性:成员变量不是public,用户只能通过public里的相应函数来访问成员变量,用户使用时就都有一致的使用规则(全都要使用小括号等)

使用函数可以对成员变量的处理有更精确的控制,如可以编写const函数实现只读访问,不加const实现读写访问等

封装性,防止成员变量被更改

假如有一个public成员变量,我们最终取消了它,所有使用它的代码都会被破坏,假如有一个protected成员变量,我们最终取消了它,所有使用它的派生类都会被破坏。因此protected其实并不比public更加具有封装性

说到底,选择private就好

以non-member non-friend替换member函数

能够访问private成员变量的函数只有class的member函数加上friend函数,如果要在一个member函数(不只可以访问private数据,也能取用private函数、enums、typedefs等)和一个non-member,non-friend函数做抉择,较好封装性的时后者。因为它并不增加能够访问class内private成分的函数数量

将所有便利函数放在多个头文件内但同属于一个命名空间,用户可以轻松添加这一组便利函数,即可以添加更多的non-member,non-friend函数到此命名空间

参考C++标准程序库,vector、algorithm等,导入头文件再进行调用,即可完成很多事情

non-member

若所有参数皆需要类型转换,请为此采用non-member函数

class Rational{
public:
 Rational(int numerator=0,int denominator=1);
 int numerator() const;
 int denominator() const;
 const Rational operator* (const Rational& rhs) const;
}
Rational onehalf(1,2);
Rational result=onehalf*2;//很好!
Rational result=2*onehalf;//不行!

原因在于:

result=onehalf.operator*(2);//发生了隐式转换 得益于之前没有将构造函数声明为explicit
result=2.operator*(onehalf);

2没有相应的class,没有operator*成员函数,当然无法执行

结论为:只有当参数被列于参数列内,这个参数才是隐式转换的合格参与者

改变做法为将operator*变成non-member函数,允许编译器在每个实参上执行隐式转换

operator*是否应该成为class的一个friend函数呢?否定的,因为operator*完全可以借用Rational的public接口完成任务,这告诉我们:member函数的反面是non-member,而不是friend

如果你需要为某个函数的所有参数(包括this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是non-member

考虑写出一个不抛出异常的swap函数

当做swap时,如果交换内部的指针,效率就高了呀

以指针指向一个对象,内含真正的数据,即pimpl手法(pointer to implementation)

class WidgetImpl{
public:
 ...
private:
 int a,b,c;
}
class Widget{
public:
 void swap(Wideget& other){
 using std::swap;//必要的,在找不到class里的swap函数调用此函数
 swap(p,other.p);
 }
private:
 WidgetImpl* p;
}
//修订后的std::swap特化版本
namespace std{
 template<>
 void swap<Widget>(Widget& a,Widget& b)
 {
 a.swap(b);//调用a的swap成员函数
 }
}

这种方法和STL有一致性,因为STL也提供有public的成员函数和std::swap的特化版本

如果swap的默认版本的效率你可以接受,那不需要做任何事

如果swap的默认版本实现效率不足:

1、提供一个public swap成员函数,让它高效的置换两个对象(pimpl)

2、在class的命名空间内提供一个non-member swap,并用它调用上诉swap成员函数。

3、如果正在编写一个class,为class特化std::swap,并用它调用你的swap成员函数。

作者:RolleX原文地址:https://blog.csdn.net/RolleX/article/details/126982795

%s 个评论

要回复文章请先登录注册