zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

【Effective C++】实现

2023-02-18 16:27:19 时间

大多数情况下,适当提出你的 classes(和 class templates)定义以及 functions(和 function templates)声明,是花费最多心力的两件事。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是有些东西需要小心。太快定义变量可能造成效率上的延迟;过度使用转型(casts)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象 “内部数据之号码牌(handles)” 可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄露和数据破坏;过度热心地 inlining 可能引起代码膨胀;过度耦合(couping)则可能导致让人不满意的冗长建置时间(build times)。

所有这些问题都可避免。本章足以解释各种做法。

条款26:尽可能延后变量定义式的出现时间

只要定义一个变量而其类型带构造或者析构函数,那就要承担构造或者析构的成本,即使是这个变量没有被使用,比如:

std::string encryptPassword(const std::string & password)
{
    using namespace std;
    string encrypted;
    if(password.length() < MinimumPasswordLength)
    {
        throw logic_error("Password is too short");
    }
    ...
    return encrypted;
}

如果有异常抛出,encrypted就完全没有被使用,此时你仍得付出encrypted的构造成本和析构成本。且使用默认构造函数出一个对象然后对它赋值效率比较差,更好的做法是:password作为encrypted的初值,跳过毫无意义的默认构造过程

std::string encryptPassword(const std::string & password)
{
    using namespace std;
    if(password.length() < MinimumPasswordLength)
    {
        throw logic_error("Password is too short");
    }
    string encrypted(password);
    ...
    return encrypted;
}

尽可能延后的真正意义是:你不应该只延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义知道能够给它初值实参为止。

但是循环怎么办?考虑下面两个结构,哪一个比较好?

//做法A:定义在循环外,每次循环迭代时赋值给它
Widget w;
for(int i = 0; i < n; ++i)
{
    w = 取决于i的某个值
    ...
}
//做法B:定义在循环内
for(int i = 0; i < n; ++i)
{
    Widget w(取决于i的某个值);
        ...
}

做法A:1个构造函数 + 1个析构函数+ n个赋值操作
做法B : n个构造函数+n个析构函数
如果赋值操作低于构造和析构成本,A比较好,尤其当n比较大的时候,否则做法B可能会更好。此外做法A造成w的作用域比做法B更大,有时会对程序的可理解性和易维护性造成冲突。

条款27:尽量少做转型动作

  • 旧式C风格转型:
  • (T)expression T(expression)
  • 新式转型:
const_cast<T>(expression)//通常用来将对象的常量性移除.
dynamic_cast<T>(expression)//主要用来执行安全向下转型,也就是用来决定某个对象是否归属继承体系中的某个类型.
reinterpret_cast<T>(expression)//意图执行低级转型,比如将一个int*转换成一个int
static_cast<T>(expression)//用来强迫隐式转换,比如将no-const对象转换为const对象,将int转为double等等.

旧式转型仍然合法,但新式转型比较受欢迎.原因是:
1.它们很容易在代码中被辨识出来
2.转型动作的目标越窄化,编译器越可能诊断出错误的运用。比如:如果你打算将常量性去掉,除非使用const-cast否则无法通过编译。

旧式转型使用的时机通常在explicit构造函数中。比如:

class Person
{
    public:
        explicit Person(int age);//explicit防止类构造函数的隐式自动转化
}
...
void doSomeWork(const Person& p);
Person p = 21;//错误
Person p(21);//可以
doSomeWork(Person(15));//√ 感觉更通情达理
doSomeWork(static_cast<Person>(15));//√ 但是用新式转型更好

总结

1.尽量避免转型,特别是在注重效率的代码中避免dynamic-cast。如果有个设计需要转型动作,试着发展不需要转型的替代设计。
2.如果转型是必要的,试着将它隐藏于某个函数背后,将转型动作隔离,客户随后可以调用该函数,而不需将转型放进它们的代码内。
3.宁可使用C++风格(新式)的转型,也不要使用旧式转型。