成都高标建设有限公司官方网站,怎样做网站发帖,注册企业视频号,大型网站服务器多少钱文章目录 一、设计接口的原则二、使用常量对象引用做参数#xff0c;而不是使用值传递做参数三、减少能够访问类的私有变量的成员函数的数目四、运算符重载函数有的时候不应该作为类的成员函数五. 自定义 Swap 函数的艺术 一、设计接口的原则
在设计接口的时候#xff0c;尽… 文章目录 一、设计接口的原则二、使用常量对象引用做参数而不是使用值传递做参数三、减少能够访问类的私有变量的成员函数的数目四、运算符重载函数有的时候不应该作为类的成员函数五. 自定义 Swap 函数的艺术 一、设计接口的原则
在设计接口的时候尽量要明确声明参数的类型避免不必要的类型转换。 一个错误的接口设计是:
//不优雅的接口设计参数类型相同导致容易造成误用 Date(int month,int day, int year) {}
一个正确的接口设计是:
//优雅的接口设计为每一个参数限定好类型同时这也符号 struct 中的 explicit 关键字同时这里使用了常引用传参数来代替传统的按值传参数如果按值传参数程序会调用在这里插入代码片对象的拷贝构造函数构建一个在函数内作用的局部对象这个过程的开销会非常昂贵。 Date(const Month m, const Day d,const Year y): month(m.value), day(d.value), year(y.value) { if(!isValidDate()) { throw std::invalid_argument(“Invalid Date”); } }
二、使用常量对象引用做参数而不是使用值传递做参数
使用常量对象引用做参数而不是使用值传递做参数有这么一些优点: 1.防止不必要的拷贝构造函数开销传值做参数就会使用拷贝构造函数创造一个局部变量而拷贝构造函数带来的开销是巨大的。 2.防止对象切片问题
结合第一点这里给出一段实例代码:
#includeiostream
#includestring
#includestdexceptstruct Month {explicit Month(int m) : value(m) {} int value;
};struct Day {explicit Day(int d) : value(d) {} int value;
};struct Year {explicit Year(int y) : value(y) {}int value;
};class Date {
public://不优雅的接口设计参数类型相同导致容易造成误用Date(int month,int day, int year) {} //优雅的接口设计为每一个参数限定好类型同时这也符号 struct 中的 explicit 关键字同时这里使用了常引用传参数来代替传统的按值传参数如果按值传参数程序会调用对象的拷贝构造函数构建一个在函数内作用的局部对象这个过程的开销会非常昂贵。Date(const Month m, const Day d,const Year y): month(m.value), day(d.value), year(y.value) {if(!isValidDate()) {throw std::invalid_argument(Invalid Date);}}Date(const Date other)delete;Date operator(const Date other)delete;bool isValidDate() const {return month 0 month 12 day 0 day 31 year 0;}void print() const {std::cout year - month - day std::endl;}
private:int month, day, year;
};class Base {
public:std::string getName() const{return Base;}virtual void Display() const {std::cout Display Base std::endl;}
};class Son : public Base{
public:std::string getName() const{return Son;}void Display() const override{std::cout Display Son std::endl;}
};void ValueCopy(Base w) {std::cout w.getName()std::endl;w.Display();
}void ReferenceCopy(const Base w) {std::coutw.getName()std::endl;w.Display();
}int main() {try {Date date(Month(12), Day(12),Year(2023));date.print();} catch (const std::exception e) {std::cerr e.what() std::endl;}Base* bb new Son;ValueCopy(*bb);ReferenceCopy(*bb);delete bb;return 0;
}三、减少能够访问类的私有变量的成员函数的数目
在类中重要的数据通常会使用 private 关键字进行修饰来保持其良好的封装性很多时候我们为了方便喜欢使用成员函数对成员的 private 变量进行访问。然而我们应该在设计的时候尽可能减少一些没有必要的且能够访问类的私有变量的成员函数的数目这是因为这种设计出现的次数越少 我们类的封装性就越强。 假设有这样一个类
class WebBrowser {
public:...void ClearCache();void ClearHistory();void RemoveCookies();...
};如果想要一次性调用这三个函数那么需要额外提供一个新的函数
void ClearEverything(WebBrowser wb) {wb.ClearCache();wb.ClearHistory();wb.RemoveCookies();
}注意虽然成员函数和非成员函数都可以完成我们的目标但此处更建议使用非成员函数这是为了遵守一个原则越少的代码可以访问数据数据的封装性就越强。此处的ClearEverything函数仅仅是调用了WebBrowser的三个public成员函数而并没有使用到WebBrowser内部的private成员因此没有必要让其也拥有访问类中private成员的能力。
这个原则对于友元函数也是相同的因为友元函数和成员函数拥有相同的权力所以在能使用非成员函数完成任务的情况下就不要使用友元函数和成员函数。
如果你觉得一个全局函数并不自然也可以考虑将ClearEverything函数放在工具类中充当静态成员函数或与WebBrowser放在同一个命名空间中
namespace WebBrowserStuff {class WebBrowser { ... };void ClearEverything(WebBrowser wb) { ... }
}四、运算符重载函数有的时候不应该作为类的成员函数
假如我们有这一个类Rational类并且它可以和int隐式转换
class Rational { public: Rational(int n,int d) : numerator(n), denominator(d) {} Rational(int d) : numerator(0), denominator(d) {} private: int numerator; int denominator; };
当然我们需要重载乘法运算符来实现Rational对象之间的乘法
class Rational { public: … const Rational operator*(const Rational rhs) const; }; 将运算符重载放在类中是行得通的至少对于Rational对象来说是如此。但当我们考虑混合运算时就会出现一个问题
Rational oneEight(1, 8); Rational oneHalf(1, 2); Rational result oneHalf / oneEight;
result oneHalf * 2; // 正确 result 2 * oneHalf; // 报错 假如将乘法运算符写成函数形式错误的原因就一目了然了
result oneHalf.operator*(2); // 正确 result 2.operator*(oneHalf); // 报错 在调用operator*时int类型的变量会隐式转换为Rational对象因此用Rational对象乘以int对象是合法的但反过来则不是如此。
所以为了避免这个错误我们应当将运算符重载放在类外作为非成员函数
const Rational operator*(const Rational lhs, const Rational rhs);
五. 自定义 Swap 函数的艺术
由于std::swap函数在 C11 后改为了用std::move实现因此几乎已经没有性能的缺陷也不再有像原书中所说的为自定义类型去自己实现的必要。不过原书中透露的思想还是值得一学的。
如果想为自定义类型实现自己的swap方法可以考虑使用模板全特化并且这种做法是被 STL 允许的
class Widget { public: void swap(Widget other) { using std::swap; swap(pImpl, other.pImpl); } …
private: WidgetImpl* pImpl; };
namespace std { template void swap(Widget a, Widget b) { a.swap(b); } } 注意由于外部函数并不能直接访问Widget的private成员变量因此我们先是在类中定义了一个 public 成员函数再由std::swap去调用这个成员函数。
然而若Widget和WidgetImpl是类模板情况就没有这么简单了因为 C 不支持函数模板偏特化所以只能使用重载的方式
namespace std { template void swap(Widget a, Widget b) { a.swap(b); } } 但很抱歉这种做法是被 STL 禁止的因为这是在试图向 STL 中添加新的内容所以我们只能退而求其次在其它命名空间中定义新的swap函数
namespace WidgetStuff { … template class Widget { … }; … template3 void swap(Widget a, Widget b) { a.swap(b); } } 我们希望在对自定义对象进行操作时找到正确的swap函数重载版本这时候如果再写成std::swap就会强制使用 STL 中的swap函数无法满足我们的需求因此需要改写成
using std::swap; swap(obj1, obj2); 这样C 名称查找法则能保证我们优先使用的是自定义的swap函数而非 STL 中的swap函数。
C 名称查找法则编译器会从使用名字的地方开始向上查找由内向外查找各级作用域命名空间直到全局作用域命名空间找到同名的声明即停止若最终没找到则报错。 函数匹配优先级普通函数 特化函数 模板函数