const 成员函数与基于 const 的重载

const 成员函数

const 成员函数的作用是允许 const 对象使用成员函数。

以下内容来自《C++ Primer 5th》:

默认情况下 this 指针的类型是指向类类型非常量版本的常量指针。具体来说就是:StrBlob* const(StrBlob 是一个类),这也就意味着,this 指针无法指向一个常量 StrBlob 对象,也就无法在常量 StrBlob 对象上使用普通的成员函数。

所以我们想让 this 变为指向常量的常量指针,C++ 中的做法是在普通成员函数的参数列表后面加上 const 关键字。这样的成员函数叫做常量成员函数

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

基于 const 的重载

简单来说,基于 const 重载背后的思想大致是:让非常量对象使用非常量成员函数,让常量对象使用常量成员函数。

这个重载根据调用对象是否为常量,调用相应的成员函数。

下面的示例代码来自《C++ Primer 5th》:

class Screen{
public:
    Screen &display(std::ostream& os) { do_display(os); return *this; }
    const Screen &display(std::ostream& os) const {
        do_display(os);
        return *this;
    }
private:
    void do_display(std::ostream &os) const { os << contents; }
};

如果是一个常量对象,display 会调用 const Screen &display(std::ostream& os) const
如果是一个非常量对象,display 会调用 Screen &display(std::ostream& os)

一些想法

上面的内容在书中都可以找到,确实没什么意思。

在最初接触时,我有点觉得基于 const 的重载是一个有些没什么用的特性,因为无论是常量还是非常量对象都可以调用 const 成员函数,那基于 const 的重载只不过是更严格了一些,好像用处不是很大。但其实完全不是这样。

不妨看一个例子:

// StrBlob.h
#pragma once
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>

using namespace std;

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
    string& front() const;
    string& back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

这里略去了具体成员函数的部分实现。详见《C++ Primer 5th》第十二章。

#include "StrBlob.h"

int main()
{
    StrBlob b1;
    StrBlob b2 = {"a", "an", "the"};
    b1 = b2;
    b2.push_back("some");
    const StrBlob bc = {"1", "2", "3"};
    bc.front() = "4444";
    cout << bc.front() << endl;
    return 0;
}

你会发现这部分代码完全可以通过编译,而且会出现一个灾难性的问题:const 对象 bc 中的值被改变了。

这完全合法(至少我直觉上认为),因为 bc 中的 data 属性地址没变,变化的只是 data 中的一个元素。

但是这可能并不是我们想要的,我觉得我们定义了一个 const 对象,就是希望其中的数据不会被改变,就像我们定义 const int a = 42; 一样。

那问题出在哪呢?就出在这个 const 成员函数上,front() 显然返回一个字符串引用,修改一个字符串引用绑定的对象总不是错的吧?这就导致我们虽然好像定义了 const 对象,但是被我们自己的成员函数实现给否决了。

所以这时候就看出基于 const 的重载的作用了,他会根据 this 指针是否指向常量对象选择重载的成员函数,一个正确的类定义应该是下面这样:

#pragma once
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>

using namespace std;

class StrBlob{
    friend ostream& operator << (ostream&, const StrBlob&);
    friend istream& operator >> (istream&, StrBlob&);
public:
    typedef vector<string>::size_type size_type;
    StrBlob() :data() {}
    StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {}
    void push_back(const string& s) { data->push_back(s); }
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void pop_back() const;
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;
private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string& msg) const;
};

这个时候再执行上面的主函数,因为返回的是一个指向常量的字符串引用,所以并不可以对其进行赋值修改。

当然还是要具体情况具体分析,我个人感觉那些返回对象数据引用或指针的就需要使用基于 const 重载的 const 成员函数。防止对 const 对象数据进行误修改。

posted @ 2020-10-13 19:54  xinze  阅读(211)  评论(0编辑  收藏  举报