【笔记】Effective C++ 设计与声明

【笔记】Effective C++ 设计与声明

条款 18 容易正确而不容易误用的接口

class Date{
public:
	Date(int month, int day, int year);    
}
  • 容易传入错误顺序的参数
  • 容易传入无效的值

  • 接口的不一致性
  • 返回指针可能导致的内存泄露

类型系统(type system) 和 外覆类型 (wrapper types)

  • 解决方案:导入外覆类型用于区别类型,限制类型。什么是外覆类型?我的理解是在基础数据类型上包裹上一层结构体,用来区分类型。
struct Day{
    explicit Day(int d)
        :val(d){}
    int val;
};

struct Month{
    explicit Month(int d)
        :val(d){}
    int val;
};

struct Year{
    explicit Year(int d)
        :val(d){}
    int val;
};

class Date{
public:
    Date(const Month& month, const Day& day, const Year& year);
};

Date d(Month(10), Day(26), Year(1997));

书中,对于Month, Day, Year使用 class 设计成更加好的类最好。

explicit 关键字:防止隐式转换。

  • 解决方案:使用静态变量,限制有限数量的值,类似与单例模式的实现。可以使用 const 来限制类型内什么可以做。
class Month{
public:
    static Month Jan() { return Month(1);}
    static Month Feb() { return Month(2);}
    // ...
    static Month Dec() { return Month(12);}
private:
    explicit Month(int m)
        :val(m){}
    int val;
}

Date d(Month::May(), Day(30), Year(1997));
  • 解决方案:避免与内置类型不兼容 ==> 提供行为一致的接口

  • 解决方案:为了不使得客户忘记指针或者忘记使用智能指针,可以直接返回智能指针。(先发制人)

std::tr1::shared_ptr<Class> createClass()
{
    std::tr1::shared_ptr<Class> retClass(static_cast<Class>(0), getRidOfClass);
    retClass = new Class();
    return retClass;
}

shared_ptr 对于每个指针可以绑定一个 删除器, 缺省的删除器来自于当前DLL中。(淦, 我也不是很懂这一句话的意思,Visual Studio实现后报了比较多的错误,先占个坑,后续再继续)

条款 19 设计class犹如设计type

  • 新type的对象如何被创建和销毁?

    构造函数、析构函数、内存分配函数、释放函数的重载

  • 对象初始化和赋值的差别是什么?

  • 新type的对象被passed by value,意味着什么?(我也不知道我啊,这一句没理解到位)

  • 新type的合法值有哪些?

  • 新type需要配合某个继承图系?

  • 新type需要什么样的转换?(转换函数怎么写?我懵逼了)

  • 什么操作符和函数对新type合理?

  • 什么样的标准函数应该驳回?(不清楚)

  • 谁应该取用新type成员?(中间的关系有点乱,现在理不清楚)

  • 什么是新type的"未声明接口"?(什么是未声明接口?)

  • 你的新type有多么一般化?(一个顶一群,听起来挺不错的)

  • 真的需要一个新type?(这个问题为什么最后才问?难道是需要思考多点才行?)

这一条款针对class设计方面,感觉懂的很少,感触不深,但应该属于根本重点。

条款 20 宁以pass-by-reference-to-const替换pass-by-value

class Person
{
public:
    Person();
    virtual ~Person();
private:
    std::string name;
    std::string address;
}

class Student: public Person()
{
public:
    Student();
    ~Student();
private:
    std::string schoolName;
    std::string schoolAddress;
}

bool validateStudent(Student s);
Student plato;
bool platoIsOK = validateStudent(plato);
  • pass-by-value,validateStudent中会调用默认的Student构造函数,结束时析构一次,4次string的构造,Person的析构和构造。

解决方案:使用 pass-by-reference-to-const

  • 避免构造和析构。

  • 避免对象分割。

C++编译器底层,reference通过指针实现。内置对象、迭代器、函数对象 pass by value 比 pass by reference快

条款 21 必须返回对象时,莫要返回reference

函数创建对象的内存途径: stackheap

// return by value
const Rational operator*(const Rational& lhs, const Rational& rhs);

// (1) return by reference or pointer, memory in stack
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
    return result;
}

// (2) return by reference or pointer, memory in heap
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational* result = new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
    return *result;
}
Rational w, x, y, z;
w = x * y * z;

// (3)return by reference or pointer, memory in static 
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    static Rational result;
    // ... 对result操作获得结果
    return result;
}

bool operator==(const Rational& lhs, const Rational& rhs);

if((a*b) == (c*d))
{
    //..
}

(1)在函数中定义临时变量,内存区域在 stack 中。但是当函数被销毁,空间也会被销毁,指向就变成了野指针

(2)内存分配在 heap 可能造成内存泄露

(3)内存分配在 static 造成逻辑漏洞,指向同一个空间地址。

解决方案:对象之间,搬移数值的方法最直接就是通过赋值。付出的代价就是一次构造和一次析构

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

  • 从封装角度,只有 private(封装) 和 其他(不封装)
  • 数据访问的一致性、可细微划分的访问控制、允诺约束条件获得保证、提供class作者充分弹性

这一个条款我理解没有那么深刻,作者在举例public和protected权限的时候,提到的例子也不好理解。

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

  • 增加封装性、包裹弹性
  • 使用 namespace 来声明 non-member函数

准确来说,我对于namespace理解比较深刻,但是对于包裹弹性这个理解实在没意识。可能没有写到对应的代码。

条款 24 若所有的参数皆需要类型变换,请采用non-member函数

posted @ 2020-09-17 14:27  卧听惊涛骇浪  阅读(67)  评论(0)    收藏  举报