《effective c++》学习笔记(四)
2017-08-27 18:43
246 查看
# 让接口容易被正确使用,不易被误用
好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质
“促进正确使用”的办法包括接口上的一致性,以及与内置类型的行为兼容
“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等
新type的对象应该如何被创建和销毁
对象的初始化和对象的赋值有什么样的差别?
新type的对象如果被pass by value,意味着什么?
什么是新type的“合法值”
你的新type需要配合某个继承图系吗
你的新type需要什么样的转换
什么样的操作符和函数对此新type而言是合理的?
什么样的标准函数应该驳回?
谁该取用新type的成员?
什么是新type的“未声明接口”
你的新type多么一般化
你真的需要一个新type吗
Class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条款覆盖的所有讨论主题
当我们调用bar时,并且在函数中只是对f做一些访问操作(即并不需要修改f的值)时,会引起f的拷贝构造的析构操作,原因是我们以pass-by-value方式进行传递值。所以当str很大时,会造成性能上的影响。
但如果以pass-by-reference方式调用bar,则不会造成任何额外的拷贝和析构操作:
而当在bar里对f并没有修改,或者是不想改变实参的值,我们可以使用const修饰符将f保护起来:
class Foo {
// something
private:
string str;
};
void bar(const Foo &f) {
// something
}
而对于C++的内置类型(int、指针等等),或者迭代器等(内部实际是一个指针),则以pass-by-value类型传递较为高效。
尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题
以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当
Bar &foo() {
Bar b;
return b;
}
当调用foo得到的Bar对象实际上是一个未定义的值
而就算使用new在堆上申请空间,也需要用户来主动释放,实际上这并不是一个很好的设计,因为有时候还是会产生未释放的指针,考虑下面这份代码:
实际上
如果声明为static变量,也不是一个很好的方法,考虑下面这份代码:
这里的条件则会永真
实际上正确的写法应该是,直接返回一个Bar对象,而不是引用,这样就不会再出现上述的问题。
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象
protected并不比public更具封装性
为了实现int和Integer的互相转换,如果要把operator*声明为non-member,如果operator*为member函数,那么上述代码就不能通过编译
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
对于一般情况,这样的swap就可以满足要求,但对于类中有vector、string这种存储大量数据的成员、或者使用ptimpl手法时,这样拷贝实际上会消耗大量的时间,而实际上只需要置换两个对象的指针即可
所以需要做以下几个事情:
提供一个public swap成员函数,让它高效的置换你的类型的两个对象值,并且绝不应该抛出异常
在你的class或者template所在的命名空间提供一个non-member swap,并令它调用上述swap成员函数
如果你正在编写一个class,为你的class特化std::swap。并令它调用你的swap成员函数
如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸的使用swap
当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes,也请特化std::swap
调用swap时应针对std::swap使用using声明式,然后调用swap并不带任何“命名空间资格修饰”
为”用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西
好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质
“促进正确使用”的办法包括接口上的一致性,以及与内置类型的行为兼容
“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等
设计class犹如设计type
在设计一个class时,应该考虑以下几个问题:新type的对象应该如何被创建和销毁
对象的初始化和对象的赋值有什么样的差别?
新type的对象如果被pass by value,意味着什么?
什么是新type的“合法值”
你的新type需要配合某个继承图系吗
你的新type需要什么样的转换
什么样的操作符和函数对此新type而言是合理的?
什么样的标准函数应该驳回?
谁该取用新type的成员?
什么是新type的“未声明接口”
你的新type多么一般化
你真的需要一个新type吗
Class的设计就是type的设计。在定义一个新type之前,请确定你已经考虑过本条款覆盖的所有讨论主题
宁以pass-by-reference-to-const替换pass-by-value
考虑下面这份代码:class Foo { // something private: string str; }; void bar(Foo f) { // something }
当我们调用bar时,并且在函数中只是对f做一些访问操作(即并不需要修改f的值)时,会引起f的拷贝构造的析构操作,原因是我们以pass-by-value方式进行传递值。所以当str很大时,会造成性能上的影响。
但如果以pass-by-reference方式调用bar,则不会造成任何额外的拷贝和析构操作:
class Foo { // something private: string str; }; void bar(Foo &f) { // something }
而当在bar里对f并没有修改,或者是不想改变实参的值,我们可以使用const修饰符将f保护起来:
class Foo {
// something
private:
string str;
};
void bar(const Foo &f) {
// something
}
而对于C++的内置类型(int、指针等等),或者迭代器等(内部实际是一个指针),则以pass-by-value类型传递较为高效。
尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题
以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当
必须返回对象时,别妄想返回其reference
当返回一个reference时,需要考虑是否返回了一个临时的reference,例如下面这份代码:Bar &foo() {
Bar b;
return b;
}
当调用foo得到的Bar对象实际上是一个未定义的值
而就算使用new在堆上申请空间,也需要用户来主动释放,实际上这并不是一个很好的设计,因为有时候还是会产生未释放的指针,考虑下面这份代码:
Bar &operator*(const Bar &lhs, const Bar &rhs) { Bar *b = new Bar; return *b; } Bar a; Bar b; Bar c; Bar d = a * b * c;
实际上
Bar d = a * b * c;会得到两个Bar对象,但最后我们只会得到一份,另一份没有办法得到,所以也就没有办法去释放它
如果声明为static变量,也不是一个很好的方法,考虑下面这份代码:
Bar &operator*(const Bar &lhs, const Bar &rhs) { static Bar b; return b; } Bar a; Bar b; Bar c; Bar d; if (a * b == c * d) { // something }
这里的条件则会永真
实际上正确的写法应该是,直接返回一个Bar对象,而不是引用,这样就不会再出现上述的问题。
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象
将成员变量声明为private
切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性protected并不比public更具封装性
宁以non-member、non-freiend替换member函数
宁以non-memboer non-friend函数替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性若所有参数皆需要类型转换,请为此采用non-member函数
class Integer { friend Integer operator*(const Integer &lhs, const Integer &rhs); public: Integer() = default; Integer(int i) : x(i) { } private: int x; }; Integer operator*(const Integer &lhs, const Integer &rhs) { Integer res; res.x = lhs.x * rhs.x; return res; } int main() { Integer a; Integer b = 5 * a; return 0; }
为了实现int和Integer的互相转换,如果要把operator*声明为non-member,如果operator*为member函数,那么上述代码就不能通过编译
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
考虑写一个不抛异常的swap
STL库中的swap大致是这样:template<typename T> void swap(const T &lhs, const T &rhs) { T temp(lhs); lhs = rhs; rhs = temp; }
对于一般情况,这样的swap就可以满足要求,但对于类中有vector、string这种存储大量数据的成员、或者使用ptimpl手法时,这样拷贝实际上会消耗大量的时间,而实际上只需要置换两个对象的指针即可
所以需要做以下几个事情:
提供一个public swap成员函数,让它高效的置换你的类型的两个对象值,并且绝不应该抛出异常
在你的class或者template所在的命名空间提供一个non-member swap,并令它调用上述swap成员函数
如果你正在编写一个class,为你的class特化std::swap。并令它调用你的swap成员函数
如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸的使用swap
当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes,也请特化std::swap
调用swap时应针对std::swap使用using声明式,然后调用swap并不带任何“命名空间资格修饰”
为”用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西
相关文章推荐
- 《Effective C++》学习笔记条款13 以对象管理资源
- 《Effective C++》学习笔记条款23 宁以non-member、non-friend替换member函数
- 《Effective C++》学习笔记条款30 透彻了解inlining的里里外外
- 《effective c++》学习笔记(一)
- 《effective c++》学习笔记(四)
- 《effective c++》学习笔记(五)
- 《effective c++》学习笔记(七)
- 《Effective C++》学习笔记(二)
- 《Effective C++》学习笔记(三)
- 《Effective C++》学习笔记——条款21
- 《Effective C++》学习笔记——条款27
- 《Effective C++》学习笔记条款04 确定对象被使用前被初始化
- 《Effective C++》学习笔记条款06 若不想使用编译器自动生成的函数,就该明确拒绝
- 《Effective C++》学习笔记条款24 若所有参数皆需类型转换,请为此采用non-member函数
- 《Effective C++》学习笔记条款31:将文件间的编译依存关系降至最低
- 《effective c++》学习笔记(一)
- 《effective c++》学习笔记(四)
- 《effective c++》学习笔记(五)
- 《effective c++》学习笔记(七)
- 《Effective C++》学习笔记——条款43