Item 3:尽量使用常量 Effective C++笔记

Item 3: Use const whenever possible

尽量使用常量。不需多说,这是防卫型(defensive)程序设计的原则, 尽量使用常量限定符,从而防止客户错误地使用你的代码。

常量的声明

总结一下各种指针的声明方式吧:

char greeting[] = "Hello";

char *p = greeting;                    // non-const pointer, non-const data
const char *p = greeting;              // non-const pointer, const data
char * const p = greeting;             // const pointer, non-const data
const char * const p = greeting;       // const pointer, const data

const出现在*左边则被指向的对象是常量,出现在*右边则指针本身是常量。
然而对于常量对象,有人把const放在类型左边,有人把const放在*左边,都是可以的:

void f1(const Widget *pw);   // f1 takes a pointer to a constant Widget object
void f2(Widget const *pw);   // 等效

STL的iterator也是类似的,如果你希望指针本身是常量,可以声明const
iterator
; 如果你希望指针指向的对象是常量,请使用const_iterator

std::vector<int> vec;

// iter acts like a T* const
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;                              // OK, changes what iter points to
++iter;                                  // error! iter is const

//cIter acts like a const T*
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;                             // error! *cIter is const
++cIter;                                 // fine, changes cIter

返回值声明为常量可以防止你的代码被错误地使用,例如实数相加的方法:

const Rational operator*(const Rational& lhs, const Rational& rhs);

当用户错误地使用=时:

Rational a, b, c;
if (a * b = c){
    ...
}

编译器便会给出错误:不可赋值给常量。

常量成员方法

声明常量成员函数是为了确定哪些方法可以通过常量对象来访问,另外一方面让接口更加易懂: 很容易知道哪些方法会改变对象,哪些不会。

成员方法添加常量限定符属于函数重载。常量对象只能调用常量方法, 非常量对象优先调用非常量方法,如不存在会调用同名常量方法。 常量成员函数也可以在类声明外定义,但声明和定义都需要指定const关键字。
例如:

class TextBlock {
public:
  const char& operator[](std::size_t position) const   // operator[] for
  { return text[position]; }                           // const objects

  char& operator[](std::size_t position)               // operator[] for
  { return text[position]; }                           // non-const objects

private:
   std::string text;
};

TextBlock tb("Hello");
const TextBlock ctb("World");
tb[0] = ‘x‘;             // fine — writing a non-const TextBlock
ctb[0] = ‘x‘;            // error! — writing a const TextBlock

比特常量和逻辑常量

比特常量(bitwise constness):如果一个方法不改变对象的任何非静态变量,那么该方法是常量方法。 比特常量是C++定义常量的方式,然而一个满足比特常量的方法,却不见得表现得像个常量, 尤其是数据成员是指针时:

class TextBlock{
    char* text;
public:
    char& operator[](int pos) const{
        return text[pos];
    }
};

const TextBlock tb;
char *p = &tb[1];
*p = ‘a‘;

因为char*
text
并未发生改变,所以编译器认为我们的操作都是合法的。 然而我们定义了一个常量对象tb,只调用它的常量方法,却能够修改tb的数据。
对数据的操作甚至可以放在operator[]()方法里面。

这一点不合理之处引发了逻辑常量(logical constness)的讨论:常量方法可以修改数据成员, 只要客户检测不到变化就可以。可是常量方法修改数据成员C++编译器不会同意的!这时我们需要mutable限定符:

class CTextBlock {
public:
  std::size_t length() const;

private:
  char *pText;

  mutable std::size_t textLength;         // these data members may
  mutable bool lengthIsValid;             // always be modified
};                                     

std::size_t CTextBlock::length() const{
  if (!lengthIsValid) {
    textLength = std::strlen(pText);
    lengthIsValid = true;
  }
  return textLength;
}

避免常量/非常量方法的重复

通常我们需要定义成对的常量和普通方法,只是返回值的修改权限不同。 当然我们不希望重新编写方法的逻辑。最先想到的方法是常量方法调用普通方法,然而这是C++语法不允许的。 于是我们只能用普通方法调用常量方法,并做相应的类型转换:

const char& operator[](size_t pos) const{
    ...
}

char& operator[](size_t pos){
    return const_cast<char&>(
        static_cast<const TextBlock&>(*this)
            [pos]
    );
}
  1. *this的类型是TextBlock,先把它强制隐式转换为const
    TextBlock
    ,这样我们才能调用那个常量方法。
  2. 调用operator[](size_t)
    const
    ,得到的返回值类型为const
    char&
  3. 把返回值去掉const属性,得到类型为char&的返回值。


除非注明,本博客文章均为原创,转载请以链接形式标明本文地址: http://harttle.com/2015/07/21/effective-cpp-3.html

版权声明:本文为博主原创文章,转载请附上原文链接。

时间: 2024-11-10 19:16:20

Item 3:尽量使用常量 Effective C++笔记的相关文章

Effective C++ Item 2 尽量以const, enum, inline 替换 #define

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 尽量以const, enum,inline 替换 #define --> 宁可以编译器替换预处理器 1.对于单纯常量,最好以const 对象或enum替换#define 不要用 #define ASPECT_RATIO 1.653 而用 const doube AspectRatio = 1.653 两个使用const的特殊情况 1.指向常量char *的字符串的常量指针 const ch

Effective Item 9 - 尽量使可访问性最小化

模块设计是否良好,有个重要的因素在于,相对外部模块是否隐藏内部数据以及实现细节. 设计良好的模块会隐藏实现细节,并将API与其实现隔离开来. 模块之间通过API进行通信,对于内部工作情况互不可见. 即,封装(encapsulation)--软件设计的基本原则之一. 为什么要封装? 通过封装可以有效地接触各个模块之间的耦合关系,使这些模块可以独立地开发.测试.优化.使用.理解和修改. 即: ·可以增加开发效率,模块可以并行开发. ·封装可以减轻维护的负担,可以更有效的进行优化,且不会影响其他模块的

Item 18:让接口容易被正确使用,不易被误用 Effective C++笔记

Item 18: Make interfaces easy to use correctly and hard to use incorrectly. "让接口容易被正确使用,不易被误用",这也是面向对象设计中的重要概念,好的接口在工程实践中尤其重要. 在使用优秀的第三方组件时,常常能够切身感受到好的接口原来可以这么方便,甚至不需记住它的名字和参数就能正确地调用. 反观自己写的API,常常会有人三番五次地问这个参数怎么设置,真是失败.人非圣贤孰能无过,只能在这种痛苦的驱动下努力的重构和

Item 2:避免使用define Effective C++笔记

Item 2: Prefer consts, enums, and inlines to #defines 尽量使用常量.枚举和内联函数,代替#define.我们知道#define定义的宏会在编译时进行替换,属于模块化程序设计的概念. 宏是全局的,面向对象程序设计中破坏了封装.因此在C++中尽量避免它! 接着我们具体来看#define造成的问题. 不易理解 众所周知,由于预处理器会直接替换的原因,宏定义最好用括号括起来.#define函数将会产生出乎意料的结果: #define MAX(a,b)

Effective c++(笔记)之继承关系与面向对象设计

1.公有继承(public inheritance) 意味着"是一种"(isa)的关系 解析:一定要深刻理解这句话的含义,不要认为这大家都知道,本来我也这样认为,当我看完这章后,就不这样认为了. 公有继承可以这样理解,如果令class D以public 的形式继承了class B ,那么可以这样认为,每一个类型为D的对象同时也可以认为是类型为B的对象,但反过来是不成立的,对象D是更特殊化更具体的的概念,而B是更一般化的概念,每一件事情只要能够施行于基类对象身上,就一定可以应用于派生类对

Effective c++(笔记) 之 类与函数的设计声明中常遇到的问题

1.当我们开始去敲代码的时候,想过这个问题么?怎么去设计一个类? 或者对于程序员来说,写代码真的就如同搬砖一样,每天都干的事情,但是我们是否曾想过,在c++的代码中怎么样去设计一个类?我觉得这个问题可比我们"搬砖"重要的多,大家说不是么? 这个答案在本博客中会细细道来,当我们设计一个类时,其实会出现很多问题,例如:我们是否应该在类中编写copy constructor 和assignment运算符(这个上篇博客中已说明),另外,我们是让编写的函数成为类的成员函数还是友元还是非成员函数,

Effective C++笔记05:实现

条款26:尽可能延后变量定义式的出现时间 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 有些对象,你可能过早的定义它,而在代码执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本. 所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止. 这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为. 遇到循环怎么办?此时往往我

Effective c++(笔记)----类与函数之实现

上篇博客中集中说明了在设计一个类的时候常遇到的问题,当然博客中还夹杂着我随时想到的一些知识,发现自己写博客没很多人写的好,可能是自己语言不会组织,要么就是写的东西大家不愿意看,反正是有这方面的专业问题或者博客中有什么明显的错误和问题,大家提出来,我也好改进哈! 回归正题,这篇博客就大概的把Effective c++中类与函数这节看到的知识点做个笔记. 设计好一个类后,自己就要去实现这个类(实现类中的成员函数.友元.非成员函数等) 可能大家会遇到以下问题 1.在类的成员函数中,尽量避免返回内部数据

Effective C++笔记06:继承与面向对象设计

关于OOP 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtual或non-virtual或pure-virtual. 3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为