C++ const的使用

摘要

在C++中,const可以用在很多的场合,本文尝试说明const常用的几种场景,并解释为什么要这样使用。

const的含义

在进行讨论之前,先说明const是个什么东西。
const其实是一个语义约束,告诉编译器和其他程序员某值应该保持不变。在程序编译的过程中该约束由编译器保证,如果违反了const约束,编译报错。

函数形参数列表

最常使用const的大概就是在函数的参数列表了吧,如:

int max(const int a, const int b);

通常应该将函数的参数声明为const,告诉编译器和调用此函数的程序员该值在函数内部不会发生更改,因为函数传参是按值传递的,即使在函数内部修改了该值,调用者也不会知道。除非有足够的理由,否则都应该将参数声明为const,而对于自定义的对象,应该使用引用传递,而不是值传递,避免发生拷贝。使用引用时,则更需要注意const的使用,如果一个函数的行为不应该更改参数的值,就必须为其添加const约束,否则一旦在函数实现时不小心更改了该值,该行为就会反应给调用者,此时函数的实际行为与期望的行为不同,如果调用者利用了这个‘特性’,那后续是否应该修复BUG就时另一个问题了。如果函数会修改传递的参数的值,那当然不需要添加const约束,此时调用者看到函数的声明就可以知道那些值可能会发生变动,那些不会。
另一个好处是,声明为const可以让函数的适用场景更加广泛。一个non-const的对象可以安全的转换为const,而一个const对象转换为non-const则通常是不安全的,应该禁止了。

声明时:

char greeting[] = "Hello";
char* p = greeting;                 //指针不是常量,指针指向的值也不是常量
//cp是一个指针,这个指针指向了一个字符(字符指针),这个字符是个常量
const char* cp = greeting;          //指针不是常量,指向的值是常量
//pc是一个常量,这个常量是一个指针,这个指针指向一个字符
char* const pc = greeting;          //指针是常量,指向的值不是常量
//cpc是个常量,这个常量是个指针,这个指针指向了一个字符,指向的字符是个常量
const char* const cpc = greeting;   //指针是常量,指向的值也是常量

const配合指针一起使用时,有时会觉得其含义难以理清,但是只要使用一下从右到左进行分析就可以看出其含义。

const用于函数返回值

令函数返回一个常量值,可以降低因客户错误而造成的意外,而又不至于放弃安全性的高效性

如果函数的返回值是可以进行赋值操作的,如[]运算符,应该返回引用,但是如果是一个常量的对象的[],则应该返回一个常量引用,故应该有两个不同的重载:

const T& operator[](const size_t pos) const;
T& operator[](const size_t pos);

避免客户对常量对象做修改操作

对于函数应该是返回值的情况,应该不允许客户执行赋值操作,这通常会发生不符合预期的结果。
fun(a) = 10显然是不应该出现的,此时将函数返回值声明为const可以有效的防止这种情况的发生。

const作用于类成员函数时

使用const声明成员函数时,则该函数可以作用于const对象,表明该函数不会改动对象的内容,且使得操作const对象成为可能。

当两个成员函数仅仅时‘常量性’不同,也时可以被重载的。以上文提到的[]为例:

class TextBlock{
private:
    std::string text;
public:
    explicit TextBlock(const std::string& text) : text(text){}

    //operator[] for const object
    const char& operator[](const std::size_t pos) const{
        std::cout << "const operator[]" << std::endl;
        return text[pos];
    }
    //operator[] for non-const object
    char& operator[](const std::size_t pos){
        std::cout << "non const operator[] " << std::endl;
        return text[pos];
    }
};

int main(int, char**) {

    TextBlock tb("Hello");
    const TextBlock ctb("World");

    tb[0];          //调用non-const operator[]
    ctb[0];         //调用const operator[]

    return 0;
}

执行结果

non const operator[]
const operator[]

而对于

tb[0] = 'a';        //正确
ctb[0] = 'a';       //错误

的编译结果为:

error: assignment of read-only location ‘ctb.TextBlock::operator

‘bitwise constness’和’logical constness’

在继续讨论之前,需要区分下两个概念:
‘bitwise constness’:成员函数只有在不更改对象的任何成员变量时才被认为时const的
‘logical constness’:成员函数可以更改所处理对象的部分内容,只要不会被客户端侦测到即可

'bitwise constness’存在着问题:如果类里面包含了一个指向其他对象的指针,该指针的内容没有改变,但是指针指向的值发生了改变,此时对象拥有的指针没有改变,即对象的每一位数据都没有发现改变,但是对客户端而言对象确确实实发生了改变。而编译器通常也只能保证’bitwise constness’的成立。

如果部分对象采用懒加载的方式,可能在第一次查询时才会去加载数据,此时需要去更改对象的内容,但在客户端看来该对象应该不变的,即客户端不会侦测到内容的更改,但是实际上却发生了修改。但是编译器会按照’bitwise constness’的约束进行检测,不允许进行修改,此时可以通过mutable释放掉non-static成员变量的bitwise constness约束。

在const 和 non-const成员函数中避免重复

如上文提到的operator []操作,包含了const版本和non-const版本,可以发现,两个版本的代码基本是一致的,如果需要边界检验,数据完整性验证等操作,维护两个版本的代码会导致大量的重复代码

一个可行的方案是通过non-const版本的函数通过调用const版本的函数实现:

class TextBlock{
private:
    std::string text;
public:
    explicit TextBlock(const std::string& text) : text(text){}

    //operator[] for const object
    const char& operator[](const std::size_t pos) const{
        ... 
        ...
        return text[pos];
    }
    //operator[] for non-const object
    char& operator[](const std::size_t pos){
        return const_cast<char&>(
            static_cast<const TextBlock&>(*this)[pos]
        );
    }
};

上述代码使用static_cast<const T&>为添加const约束,从而调用const版本的operator [],如果不添加const,则会产生递归调用non-const版本的operator []的情况
在返回的使用又通过const_cast<T&>移除const

移除const只能通过const_cast实现,且大部分情况下都不应该使用。
const成员函数承诺不会改变对象的逻辑状态,而non-const成员函数并不保证这一点,因此从non-const成员函数中调用const成员函数是安全的,但是反过来则是非常危险的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值