zoukankan      html  css  js  c++  java
  • 【effective c++读书笔记】【第4章】设计与声明(1)

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

    1、许多客户端错误可以因为导入新类型而获得预防。在防范“不值得拥有的代码”上,类型系统是你的主要同盟国。

    2预防客户错误的另一个办法是,限制类型内什么事可做,什么事不能做。常见的限制是加上const。

    3除非有好理由,否则应该尽量令你的types的行为与内置types一致。

    4tr1::shared_ptr有一个特别好的性质是:它会自动使用它的“每个指针专属的删除器”,因而消除另一个潜在的客户错误:所谓的“cross-DLL problem”。这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个DLL内被delete销毁”。在许多平台上,这一类“跨DLL之new/delete成对运用”会导致运行期错误。tr1::shared_ptr没有这个问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个DLL”的delete。

    请记住:

    • 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
    • “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
    • “阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
    • tr1::shared_ptr支持定制删除器。这可防范DLL问题,可被用来自动解除互斥量等等。   

    条款19:设计class犹如设计type

    1、新type的对象应该如何被创建和销毁?考虑构造函数和析构函数以及内存分配函数和释放函数的设计。

    2、对象的初始化和对象的赋值该有什么样的差别?考虑构造函数和赋值操作符的行为及其间的差异。

    3、新type的对象如果被passedby value(以值传递),意味着什么?

    4、什么是新type 的“合法值”?考虑成员函数必须进行错误检查工作

    5、你的新type需要配合某个继承图系(inheritancegraph)吗?如果你的class是从别的基类继承下来的,那么会受到基类的束缚。反之,如果你允许其他classes继承你的class,会影响你声明的函数尤其是析构函数是否是virtual

    6、你的新type需要什么样的转换?显式转换或隐式转换

    7、什么样的操作符和函数对此新type而言是合理的?

    8、什么样的标准函数应该驳回?

    9、谁该取用新type的成员?成员是public,private, protected还是friend?

    10、什么是新type的“未声明接口”(undeclared interface)?

    11、你的新type有多么一般化?class template

    12、你真的需要一个新type吗?

    请记住:

    • Class的设计就是type的设计。在定义一个新的type之前,请确定你已经考虑过本条款覆盖的所有讨论主题。  

    条款20:宁以pass-by-reference-to-const替代psss-by-value

    1、缺省情况下C++以by value方式传递对象至函数。除非你另外指定,否则函数参数都是以实际实参的副本为初值,而调用端所获得的亦是返回值的一个副本。这些副本由对象的拷贝构造函数产生,这可能使得pass by value成为昂贵的操作。

    例子:
    #include<iostream>
    #include<string>
    using namespace std;
    class Person{
    public:
    	Person(){ cout << "Person()" << endl; }
    	Person(const Person& p):name(p.name),address(p.address){//显式定义,为了说明
    		cout << "Person copy construct" << endl;
    	}
    	~Person(){ cout << "~Person()" << endl; }
    private:
    	string name;
    	string address;
    };
    class Student:public Person{
    public:
    	Student(){ cout << "Student()" << endl; }
    	Student(const Student& s) :Person(s), schoolName(s.schoolName), schoolAddress(s.schoolAddress){//显式定义,为了说明
    		cout << "Student copy construct" << endl;
    	}
    	~Student(){ cout << "~Student()" << endl; }
    private:
    	string schoolName;
    	string schoolAddress;
    };
    bool validateStudent(Student s){
    	return true;
    }
    int main(){
    	Student s;
    	cout << "------------------" << endl;
    	bool  b = validateStudent(s);
    
    	system("pause");
    	return 0;
    }
    运行结果:

    说明:从运行结果我们可以对validateStudent函数的参数的传递成本是“一次Student拷贝构造函数调用和一次Student析构函数调用”。其实远不止这些,因为上述两个类都含有两个string,他们也相应的调用拷贝构造函数,就是说四次string拷贝构造动作,还有四次析构函数。所以最终结果是以by value方式传递一个Student对象会导致一次Student拷贝构造函数、一次Person拷贝构造函数、四次string拷贝构造函数。当函数内的那个Student副本被销毁时,都要调用对应的析构函数。因此,以by value方式传递一个Student对象总体成本是“六次构造函数和六次析构函数”。

    避免构造和析构的方法,就是pass by reference-to-const。

    只要把上面的例子的validateStudent函数的参数改为constStudent&即可。

    bool validateStudent(const Student& s){
    	return true;
    }
    
    运行结果:


    说明:这种传递方式的效率高得多:没有任何构造函数或析构函数被调用,因为没有任何新对象被创建。将它声明为const是必要的,防止传入的Student对象被改变。

    2、by reference方式传递参数也可避免切割问题。如果窥视c++编译器的底层,references往往以指针实现出来,因此pass by reference通常意味着这真正传递的是指针。对内置类型而言,pass by valuepass by reference-to-const时,选择pass by value并非没有道理。这个忠告也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为pass by value

    切割问题例子:

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Window{
    public:
    	virtual void display()const{
    		cout << "window display()" << endl;
    	}
    };
    class WindowWithScrollBars :public Window{
    public:
    	virtual void WindowWithScrollBars::display() const{
    		cout << "WindowWithScrollBars display()" << endl;
    	}
    };
    void pintNaandWind(Window w){
    	w.display();
    }
    
    int main(){
    	WindowWithScrollBars wwsb;
    	pintNaandWind(wwsb);
    
    	system("pause");
    	return 0;
    }
    
    运行结果:


    说明:从运行结果可以看出,我们得到的是Window的display(),而我们本想得到WindowWithScrollBars的display()。它调用的是window对象的display(),而不是子类的,因为pintNaandWind中的参数传递是by value,WindowWithScrollBars的所有特化信息都会被切除。解决办法是 pass by reference-to-const。

    void pintNaandWind(Window& w){
    	w.display();
    }

    运行结果:

    请记住:

    • 尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高效,并可避免切割问题(slicingproblem)。
    • 以上规则并不适合与内置类型,以及STL的迭代器和函数对象.对它们而言,pass-by-value往往比较适合。

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

    1、所谓reference只是个名称,代表某个既有对象。任何时候看到一个reference声明式,你都应该立刻问自己,它的另一个名称是什么?因为它一定是某物的另一个名称。

    2、函数创建新对象的途径有二:在栈空间或在堆空间创建。

    3、在栈空间创建对象例子

    class Rational{
    public:
    	Rational(int numerator, int denominator);
    	...
    private:
    	int n, d;
    	friend const Rational operator*(const Rational& lhs, const Rational& rhs);
    };
    const Rational& operator*(const Rational& lhs, const Rational& rhs){
    	Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
    	return result;
    }

    在栈空间创建:即定义一个局部变量。局部变量在函数退出前就被销毁了,若还有引用指向该局部变量,将产生“无定义行为”的后果,所以传引用在栈上不可行。

    4、在堆空间创建对象例子:

    const Rational& operator*(const Rational& lhs, const Rational& rhs){
    	Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    	return result;
    }

    在堆上构造一个对象,并返回reference指向它。会出现问题:谁该对着被你new出来的对象实施delete呢?还有一个问题:如果有计算:Rational result = x*y*z,其中x、y、z都是Rational类型的对象,此时就会产生内存泄露的问题。因为同一语句内调用了两次operator*,因而使用两次new,也就需要两次delete,但却没有合理的办法让operator*使用者进行那些delete调用,因为没有办法取得operator*返回的references背后隐藏的那个指针。

    5、有一种办法可以避免任何构造函数被调用:“让operator*返回的reference指向一个被定义于函数内部的static Rational对象”。造成对多线程安全性的疑虑。还会出现以下问题:

    bool operator==(const Rational& lhs, const Rational& rhs);
    Rational a, b, c, d;
    if ((a * b) == (b * c)){
    	...
    }else{
    	...
    }
    上述代码中(a * b) == (b * c)永远为真。因为两次operator*调用确实改变了static Rational对象值,但由于他们返回的都是reference,因此调用端看到的永远是static Rational对象的现值。

    6、一个“必须返回新对象”的函数的正确写法是:就让那个函数返回一个新对象。

    请记住:

    • 绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。 

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    mongoDB 获取系统时间
    android应用程序监听SMS Intent广播
    安卓开发之探秘蓝牙隐藏API
    Android内存优化解决 资料和总结的经验分享
    android 访问SMS短信收件箱
    Android 和 PHP 之间进行数据加密传输
    Android开发 MMS支持 创建和编辑MMS
    android 动态改变屏幕方向
    Android 使用 Gmail 来发送邮件
    android数据库操作之直接读取db文件
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785444.html
Copyright © 2011-2022 走看看