14-6 访问函数

在之前的第14.5节——公共与私有成员及访问限定符中,我们讨论了公共和私有访问级别。需要提醒的是,类通常将数据成员设为私有,而私有成员无法被公共直接访问。

请看以下Date类:

#include <iostream>

class Date
{
private:
    int m_year{ 2020 };
    int m_month{ 10 };
    int m_day{ 14 };

public:
    void print() const
    {
        std::cout << m_year << '/' << m_month << '/' << m_day << '\n';
    }
};

int main()
{
    Date d{};  // create a Date object
    d.print(); // print the date

    return 0;
}

虽然该类提供了用于打印完整日期的print()成员函数,但这可能无法满足用户的需求。例如,当Date对象的用户需要获取年份时该怎么办?或者需要将年份修改为其他值时又该如何?由于m_year是私有成员(因此无法被公共成员直接访问),用户将无法实现这些操作。

对于某些类而言,在符合类功能逻辑的前提下,允许获取或设置私有成员变量的值是合理的。


访问函数

访问函数是一种简单的公共成员函数,其作用是获取或修改私有成员变量的值。

访问函数分为两种类型:获取器和设置器。获取器Getters(有时也称为访问器accessors)是返回私有成员变量值的公共成员函数。设置器Setters(有时也称为修改器mutators)是设置私有成员变量值的公共成员函数。

术语说明Nomenclature:
“修改器”一词常与“设置器”互换使用。但广义而言,修改器指任何能修改modifies(修改mutates)对象状态的成员函数。按此定义,设置器是修改器的一种特殊类型。但非设置器函数也可能属于修改器范畴。

获取器通常声明为 const,以便在 const 和非 const 对象上均可调用。设置器应声明为 non-const,以便修改数据成员。

为便于说明,我们更新 Date 类使其具备完整的获取器和设置器:

#include <iostream>

class Date
{
private:
    int m_year { 2020 };
    int m_month { 10 };
    int m_day { 14 };

public:
    void print()
    {
        std::cout << m_year << '/' << m_month << '/' << m_day << '\n';
    }

    int getYear() const { return m_year; }        // getter for year
    void setYear(int year) { m_year = year; }     // setter for year

    int getMonth() const  { return m_month; }     // getter for month
    void setMonth(int month) { m_month = month; } // setter for month

    int getDay() const { return m_day; }          // getter for day
    void setDay(int day) { m_day = day; }         // setter for day
};

int main()
{
    Date d{};
    d.setYear(2021);
    std::cout << "The year is: " << d.getYear() << '\n';

    return 0;
}

这打印:

image


访问函数命名

访问函数的命名并无统一规范。不过,某些命名约定比其他约定更受欢迎。

  • 以“get”和“set”为前缀:
int getDay() const { return m_day; }  // getter
void setDay(int day) { m_day = day; } // setter

使用“get”和“set”前缀的优势在于,它明确表明这些是访问函数(且调用成本应较低)。

  • 无前缀:
int day() const { return m_day; }  // getter
void day(int day) { m_day = day; } // setter

这种风格更为简洁,获取器和设置器使用相同名称(依靠函数重载来区分二者)。C++标准库采用此约定。

无前缀约定的缺点在于,难以直观识别这是在设置day成员的值:

d.day(5); // does this look like it's setting the day member to 5?

关键洞察
在私有数据成员前添加“m_”前缀的最佳理由之一,是避免数据成员与获取器出现同名情况(C++不支持这种情况,但Java等其他语言支持)。

  • 仅限“set”前缀:
int day() const { return m_day; }     // getter
void setDay(int day) { m_day = day; } // setter

上述选择取决于个人偏好。但我们强烈建议为设置器使用“set”前缀,获取器可使用“get”前缀或不加前缀。

提示:
为设置器添加“set”前缀,可更清晰地表明其正在改变对象状态。

获取器应通过值或const左值引用返回

获取器应提供数据的“只读”访问权限。因此最佳实践是:若成员复制成本低则按值返回,若成员复制成本高则通过const左值引用返回。

由于通过引用返回数据成员涉及复杂议题,我们将在第14.7课——返回数据成员引用的成员函 中详细探讨该主题。

访问函数的考量

关于何时应使用或避免使用访问函数,业界存在诸多讨论。许多开发者认为使用访问函数违背了良好的类设计原则(这个话题足以写成整本书)。

目前我们建议采取务实策略。创建类时请考虑以下要点:

  • 若类无不变性且需大量访问函数,可考虑改用结构体(其数据成员设为public),直接提供成员访问权限。
  • 优先实现行为或操作而非访问函数。例如,替代setAlive(bool)设置器,可实现kill()和revive()函数。
  • 仅在用户确实需要获取/设置单个成员值时才提供访问函数。

既然要提供公共访问函数,为何还要将数据设为私有?

问得好!这个问题将在第14.8课——数据隐藏(封装)的优势中解答。

posted @ 2025-12-28 06:45  游翔  阅读(38)  评论(0)    收藏  举报