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

    条款22:将成员变量声明为private

    1、 如果成员变量不是public,客户唯一能够访问对象的办法就是通过成员函数。如果public接口内的每样东西都是函数,客户就不需要再打算访问class成员时迷惑地试着记住是否该使用小括号。

    2、使用函数可以让你对成员变量的处理有更精确的控制。如果你令成员变量为public,每个人都可以读写它,但如果你以函数取得或设定其值,你就可以出现“不准访问”、“只读访问”以及“读写访问”。

    3、将成员变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性。例如这可使得成员变量被读或写时轻松通知其它对象、可以验证calss的约束条件以及函数的前提和事后状态、可以在多线程环境中执行同步控制......等等。

    4、从封装的角度,其实只有两种访问权限:private(提供封装)和其他(不提供封装)。

    请记住:

    • 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保护,并提供class作者以充分的实现弹性。
    • protected并不比public更具封装性。

    条款23:宁以non-member、non-friend替换member函数

    1、面向对象守则要求,数据以及操作数据的那些函数应该被捆绑在一起,这意味着它建议member函数是较好的选择。这个建议并不正确,这是基于面向对象真实意义的一个误解。面向对象守则要求数据应该尽可能被封装,与直观相反,member函数带来的封装性比non-member函数低。

    2、如果在一个member函数(它不仅可以访问class内private数据,也可以取用private函数、enums、typedefs等等)和一个non-membernon-friend函数(它无法访问上述任何东西)之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是non-member non-friend函数,因为它并不增加“能够访问class内之private成分”的函数数量。

    3、将所有便利函数放在多个头文件内但隶属同一个命名空间,意味客户可以轻松扩展这一组便利函数。需要做的就是添加更多non-member non-friend函数到此命名空间内。

    请记住:

    • 宁可拿non-member non-friend函数替代member函数。这样做可以增加封装性、包裹弹性和机能扩充性。 

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

    例子:

    #include<iostream>
    using namespace std;
    
    class Rational{
    public:
    	Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//构造函数刻意不为explicit,为了隐式类型转换
    	int getNumerator() const{ return numerator; }
    	int getDenominator() const{ return denominator; }
    	const Rational operator*(const Rational& rhs) const{
    		return Rational(numerator*rhs.getNumerator(), denominator*rhs.getDenominator());
    	}
    private:
    	int numerator;
    	int denominator;
    };
    
    int main(){
    	Rational oneEighth(1, 8);
    	Rational oneHalf(1, 2);
    	Rational result = oneHalf*oneEighth;
    	result = oneHalf * 2;//正确,2被隐式转换为Rational(2,1)
    	//result = 2 * oneHalf; //错误
    
    	system("pause");
    	return 0;
    }
    上述例子中,
    result = oneHalf * 2;//正确,2被隐式转换为Rational(2,1)
    result = 2 * oneHalf; //错误
    

    结论是只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”——即this对象——的那个隐喻参数,绝不是隐式转换的合格参与者。让operator*成为一个non-member函数,使允许编译器在每一个实参身上执行隐式类型转换:

    #include<iostream>
    using namespace std;
    
    class Rational{
    public:
    	Rational(int n = 0, int d = 1) :numerator(n), denominator(d){}//构造函数刻意不为explicit,为了隐式类型转换
    	int getNumerator() const{ return numerator; }
    	int getDenominator() const{ return denominator; }
    private:
    	int numerator;
    	int denominator;
    };
    const Rational operator*(const Rational& lhs, const Rational& rhs){
    	return Rational(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
    }
    int main(){
    	Rational oneEighth(1, 8);
    	Rational oneHalf(1, 2);
    	Rational result = oneHalf*oneEighth;
    	result = oneHalf * 2;//正确
    	result = 2 * oneHalf; //正确
    
    	system("pause");
    	return 0;
    }

    请记住:

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

    条款25:考虑写出一个不抛异常的swap函数

    1、如果swap的缺省实现码对你的class或class template提供了可接受的效率,你不需要额外做任何事。任何尝试置换那种对象的人都会取得缺省版本,而那将有良好的运作。

    namespace std{
    	template<typename T>
    	void swap(T&a, T&b)        //std:swap的典型实现
    	{                           //置换a和b的值
    		T temp(a);
    		a = b;
    		b = temp;
    	}
    }

    2、如果swap缺省实现版的效率不足,那几乎总是意味着class或class template使用了某种pimpl手法(pointer to implementation),pimpl手法指“以指针指向一个对象,内含真正数据”那种类型的设计的表现形式。

    class WidgetImpl{
    public:
    	...
    private:
    	int a, b, c;
    	std::vector<double> v;
    	...
    };
    class Widget{ //这个class使用pimpl手法
    public:
    	Widget(const Widget& rhs);
    	Widget& operator=(const Widget&rhs)
    	{
    		...
    		*pImv = *(rhs.pImpl);
    		...
    	}
    	...
    private:
    	WidgetImpl* pImv; 
    };

    试着做以下事情:

    a、提供一个public swap成员函数,让它高效地置换你的类型的两个对象值。

    class Widget{
    public:
    	...
    	void swap(Widget& other)
    	{
    		using std::swap;
    		swap(pImpl, other.pImpl);
    	}
    	...
    };

    b、在你的class或template所在的命名空间内内提供一个non-member swap,并令它调用上述swap成员函数。

    namespace WidgetStuff{
    	...
    	template<typename T>
    	class Widget { ... }
    	...
    	template<typename T>
    	void swap(Widget<T>& a, Widget<T>& b)
    	{
    		a.swap(b);
    	}
    }

    c、如果正在编写的是一个class(而非class template),为你的class特化std::swap,并让它调用你的swap member函数。

    namespace std{
    	template<>
    	void swap<Widget>(Widget& a, Widget& b)
    	{
    		a.swap(b);
    	}
    }

    d、最后,如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数中曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。

    请记住:

    • 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
    • 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于class(而非templates),也请特化std::swap。
    • 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。
    • 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。   

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

  • 相关阅读:
    自己写的一个ASP.NET服务器控件Repeater和GridView分页类
    c#Udp分包组包方法
    利用反射写的,可以插件的俄罗斯方块
    冰之随笔一(c#反射、特性)
    Socket的简单例子
    HTTP状态码
    C# WebService 基础实例
    Win7上IIS发布网站系统部署项目
    FileUpload 简单上传+小预览
    .net 验证码
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785443.html
Copyright © 2011-2022 走看看