Effective C++ 《设计与声明》

第18条 使接口易用,不易被误用

  • 设计的接口应清晰,不易被误用
  • 可以通过接口的一致性内置类型等促进接口的正确使用
  • 可以通过建立新类型、限制类型的操作、束缚对象值等消除用户的资源管理责任
  • 对于指针等资源,善用智能指针

通过限制类型来提高接口的易用性:

// 原型
class Date{
	Date(int year, int month, int day);
};

问题 : 容易传错参数顺序
使用内建类型

class Date{
	Date(Year year, Month month, Day day);
};

struct Year{
	explicit Year(int y) : _y(y) {...}
	int _y;
};

智能指针解决手动释放、特化delete、cross-DLL problem

  • 当某资源需要特化其释放时,可以使用智能指针的自定义释放函数
auto deleter = [](A *) {};
unique_ptr<A, decltype(deleter)> p1(new A, deleter);
shared_ptr<A> p2(new A, deleter);
  • cross-DLL problem
    这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个 DLL 内被 delete销毁”。在许多平台上,这一类“ DLL之 new/delete 成对运用”会导致运行期错误。trl::shared ptr没有这个问题,因为它缺省的删除器是来自“trl::shared ptr诞生所在的那个DLL”的 delete

第19条 设计class如设计新type

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

  • 尽量以 pass-byreference-to-const 替换 pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)

  • 以上规则并不适用于内置类型,以及 STL 的迭代器和函数对象。对它们而言pass-by-value往往比较适当。

pass-by-value的性能开销更大

class H {
public:
    H() {}

    H(const H &) { cout << "Call copy construct " << endl; }

    H &operator=(const H &) { cout << "Call copy " << endl; }

    ~H() { cout << "Delete H " << endl; }
};

void h1(H) {

}

void h2(const H &) {

}

int main() {
    H h;
    h1(h);
    cout << "----" << endl;
    h2(h);
    cout << "----" << endl;
}

Call copy construct
Delete H
----
----
Delete H

函数pass-by-value调用一次,将承担拷贝构造的消耗,并在结束时承担析构消耗.

pass-by-reference 可避免切割问题

在多态中,常有子类继承基类. 接口函数多声明为基类类型,通过引用调用可根据其实际类型调用对应函数,而pass-by-value则会将派生类切割为基类(其表现为基类).

class Base {
public:
    virtual void show() const {
        cout << "Call Base" << endl;
    }
};

class Derived : public Base {
public:
    virtual void show() const override {
        cout << "Call Derived" << endl;
    }
};

void show1(const Base &base) {
    base.show();
}

void show2(Base base) {
    base.show();
}

int main() {
    Derived derived;
    show1(derived);
    show2(derived);
}

Call Derived
Call Base

第21条 必须返回对象时,不要返回其reference

  • 绝对不要返回:
    • ①local stack对象的指针或引用
    • ②heap-allocated对象的引用
    • ③可能需要多个同样对象的local static对象的指针或引用

常见误用场景

  • 返回local stack对象
A& get(int i){
	A result(i);
	return result;
}

这里返回的是局部栈上对象,在函数结束时就被析构.

  • 返回了heap-allocated
H &get(int i) {
    auto p = make_shared<H>(i);
    return *p;
}

H &get_2(int i) {
    H *p = new H(i);
    return *p;
}

int main() {
    auto &obj1 = get(1);
    auto &obj2 = get_2(2);
}

// 输出
Delete H 1

问题是谁对result进行delete?

第22条 将成员声明为private

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

第23条 宁可以non-friend, non-member替代member函数

  • 以non-friend, non-member替代member函数可以增加封装性、包裹弹性和技能扩充性

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

class WebBrowser{
public:
	void clearA();
	void clearB();
	void clearC();
	...
	void clearAll_1(){
		clearA();
		clearB();
		clearC();
	}
};

// 有限选择non-member函数
void clearAll_2(const WebBrowser& wb){
	wb.clearA();
	wb.clearB();
	wb.clearC();
}

将所有便利函数放在多个头文件内但隶属同一个命名空间,意味客户可以轻松护展这一组便利函数。

第24条 若所有参数皆需要类型转换,则采用non-member函数

例如在隐式转换中

class Rational {
public:
    Rational(double _i = 0) : i(_i) {}

    double i;

    const Rational operator*(double j) { return Rational(i * j); }
};

const Rational operator*(const Rational &a,const Rational &b) {
    return Rational(a.i * b.i);
}

member函数不支持 例如 2*a这样的操作。

第25条 写出不抛出异常的swap函数

原文的内容在C++11中可通过右值这个概念实现

template<typename T>
void Swap(T &a, T &b) {
    T&& temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值