Xander's Wiki


  • Home

  • Tags

  • Categories

  • Archives

  • Search

3.尽量以const,enum,inline替换#define

Posted on 2018-04-06 | In Effective C++

众所周知,使用const来替换#define定义的常量是一个不错的选择,当我们以常量替换预处理时,有两点值得注意:

1.常量指针
在定义常量指针时,由于常量定义式通常置于头文件内,同时在多次调用时为了避免不必要的修改,因此有必要将指针声明为const,而非指针所指的对象。如果你需要将指向对象也声明为const,则需要两个const.举例而言:

1
const char* const authorname = “mie”; 

当然,这里使用string比使用char-based字符串更合适。
2.class的专属常量
在定义class的专属常量时,为了将变量的作用域仅仅局限在class内,我们将其作为class的一个成员。为了确保此常量至多只有一份实体,我们将其设为static。举例而言:

1
static const int sc = 5; 

值得注意的是,此处只是sc的声明而非定义。通常意义下,c++需要你对你所使用的任何东西提供一个定义式。
*但如果它是一个class的static const member且为intergral type(int,char,bool),则需要特殊处理。

如果你无需获取地址,那你可以只声明不定义。但如果你需要使用地址或者你的编译器非要看到一个定义式,则应该另外提供定义式如下:
1
const int GamePlayer::NumTurns; 

由于const class member在声明时获得了初值,因此在定义时不可再设初值。


如果class在编译期间需要一个class常量,比如说数组声明式。那么我们也没必要非要用#define。
我们可以使用enum hack来提供一个整形数据,因为一个枚举类型的数值总可以充当int被使用,例如:

1
2
enum {NumTurns = 5}; 
int scores[NumTurns];

这样写有两个好处:

  1. enum的行为比较像#define而不是const,比如你无法对它取址,也无法有一个pointer或者reference指向它。同时,避免使用了额外的内存空间以存储const int.
  2. 实用主义 很多代码使用了它

#define还有一个作用就是有人喜欢用它写宏。实际上这样根本不方便而且难于维护。我们有更好的替代方法:template inline函数


总结:

  1. 对于单纯常量,最好用const对象与enum替换#define
  2. 对于形似函数的宏,最好使用inline函数替代

3.确保容器中的对象副本正确且高效

Posted on 2018-04-06 | In Effective STL

容器中保存的对象并不是你放进去的对象,取出的对象也不是容器内的对象,它们都是副本。
这种copy in,copy out就是STL 容器的工作方式。
一旦对象放入容器,它们可能会遭遇更多的复制,比如顺序容器内遭遇排序或者插入,删除,它们元素都会进行一次复制操作。复制操作由拷贝构造函数与拷贝构造运算符完成。那么问题来了,如果对象的复制操作很费时,填充对象这个操作可能会降低程序性能,而且放入的副本如果有特殊含义,可能会导致出错。
在存在继承关系的情况下,copy会导致slicing,举例而言,容器元素类型是bc,但我们试图放入dc对象,那么它的特有部分将会丢失。
总之,使copy操作高效且正确的方法是在容器内部包含指针而不是对象。当然,指针也有一些问题,后期我们会发现智能指针更好用。
听起来STL容器的疯狂复制这种行为很蠢,但是相对于array,它的可塑造性强了很多,我们在使用时只需要它总是在复制并且总是应该向其中加入对象的指针即可。

​

2.视C++为语言联邦

Posted on 2018-04-06 | In Effective C++

C++作为一个多重范型编程语言,应该将其视作一个语言联邦而非单一语言,其主要的sublanguage共有四个:

  • c
  • object-oriented c++
  • template c++
  • STL

四个次语言各有特性及约束,C++高效编程原则视情况而定,具体取决于我们当前使用的那一部分。

2.不要试图编写独立于容器的代码

Posted on 2018-04-06 | In Effective STL

​
STL本身就是以泛化原则作为基础:

  • 数组被泛化为“以包含对象的类型”为参数的容器
  • 函数被泛化为“以使用的迭代器的类型”为参数的算法
  • 迭代器被泛化为“以其指向的对象的类型”为参数的迭代器

有人可能会试图去开发某种程序,它对任何容器都适用,而这是无必要也是不可能的。不同容器应用场合不同,其特性也不同,我们无法再次对其泛化。
但是,我们也许会在某一天发现自己选取的容器并非最佳容器,所以我们试图去改变它。这个时候封装技术就显得很有必要。


封装技术最简单的实现方式就是使用类型定义typedef。具体来说,假设原有代码如下:

1
2
3
4
5
class Widget {...};
vector<Widget> vw;
Widget bestWidget;
... // 给bestWidget一个值
vector<Widget>::iterator i = find(vw.begin(), vw.end(), bestWidget);//寻找等值者

这一种写法相当常见,但下一种更好:
1
2
3
4
5
6
7
class Widget { ... };
typedef vector<Widget> WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
...
WCIterator i = find(cw.begin(), cw.end(), bestWidget);

最明显的一个好处就是更换容器时代码无需发生改动。


当然,类型定义只是词法上的修改,它并未增加什么功能。如果我们不希望让用户得知我们真正使用的容器,那我们则需要使用class来完成。要想减少在替换容器后需要修改的代码,我们可以把容器隐藏在一个class中,并且尽量减少通过类接口(而使外部)可见的,与容器相关的信息。
举例而言,如果我们试图编写一个顾客列表,不直接使用list,而是新建一个customerlist类,并把list隐藏于私有部分,其具体实现可以如下所示:

1
2
3
4
5
6
7
8
class CustomerList {
private:
typedef list<Customer> CustomerContainer;
typedef CustomerContainer::iterator CCIterator;
CustomerContainer customers;
public: // 通过该接口限制list的可见性
...
};

这样既保证了list的私密性,也使代码易于修改。

1.仔细选择容器

Posted on 2018-04-05 | In Effective STL

可选容器:

  • 标准STL序列容器:vector,list,sequence
  • 标准STL关联容器:set,multiset,map,mutimap
  • 非标准序列容器slist与rope
    其中,slist是单向链表,rope为重型string
  • 非标准关联容器hash_set,hash_mutiset,hash_map,hash_mutimap
  • vector作为string的替代
  • 标准非STL容器,诸如array,bitset,stack之类

在选择容器时需要顾虑的有很多,vector,list,deque之间的选择也就不说了,大家都懂。


根据内部元素物理地址的相互关联性,STL容器可以分为两类

1.连续内存容器
2.基于节点的容器

这两种容器其实也说得很多了,在这里要强调的是rope是连续的,而关联容器和散列容器是基于节点的。


选择容器时需要判断的一些依据

  • 是否需要在任意位置插入?
    如果是则不可选择关联容器
  • 容器内元素是否需要排序?
    是则不可选择散列容器
  • 是否必须是标准c++?
    否则不可使用slist,rope与散列容器
  • 迭代器是否有要求?
    随机访问迭代器需要vector,string,deque.slist不可以使用双向迭代器
  • 是否需要频繁插入删除?
    是则选择基于节点
  • 是否需要兼容c接口?
    是则选择vector
  • 是否对查找速度有要求?
    是则选择散列容器,sorted vector以及标准关联容器
  • 是否介意容器内部的引用计数?
    是则需要避免string与rope
  • 插入和删除如果失败是否需要回滚?
    是则需要选择基于节点的容器,如果是区间形式的操作,那只能使用list.
  • 是否需要尽可能少地让迭代器失效?
    是则选择基于节点的容器(他们的插入和删除操作从来不会让迭代器,指针,或者引用失效)
    deque的插入操作仅发生在末尾时,迭代器可能失效,但指针和引用不会失效,它是唯一会有这种情况的容器。

1.导读

Posted on 2018-04-05 | In Effective C++
  • 除非你希望构造函数发生隐式转换,否则请将其声明为explicit。
  • 针对拷贝构造函数和拷贝运算符=,必须注意=可以调用构造函数。
    因此,当我们新建一个对象时必然是调用了构造函数,而不是赋值操作。
    同理,当有对象存在时一定是赋值,而绝非拷贝。
  • 针对用户自定义的类型,通常不使用pass-by-value,而是使用pass-by-reference-to-const。
  • ctor与dtor是构造函数与析构函数的缩写。
  • rhs:right-hand side lhs亦是如此
<i class="fa fa-angle-left"></i>1…2627

xander.liu

266 posts
11 categories
36 tags
RSS
GitHub E-Mail
© 2024 xander.liu
Powered by Hexo
|
Theme — NexT.Pisces v5.1.4