14-4 const 类对象和 const 成员函数
在第5.1节——常量变量(命名常量)中,你了解到基本数据类型(如int、double、char等)的对象可通过const关键字定义为常量。所有const变量必须在创建时初始化。
const int x; // compile error: not initialized
const int y{}; // ok: value initialized
const int z{ 5 }; // ok: list initialized
同样地,类类型对象(结构体、类和联合体)也可通过const关键字声明为常量。此类对象同样必须在创建时完成初始化。
struct Date
{
int year {};
int month {};
int day {};
};
int main()
{
const Date today { 2020, 10, 14 }; // const class type object
return 0;
}
与普通变量类似,当需要确保对象创建后不可修改时,通常应将类类型对象声明为 const(或 constexpr)。
禁止修改const对象的数据成员
一旦const类型的对象被初始化,任何试图修改该对象数据成员的行为均被禁止,因为这将违反对象的const属性。这包括直接修改成员变量(若其为public类型),或调用会设置成员变量值的成员函数。
struct Date
{
int year {};
int month {};
int day {};
void incrementDay()
{
++day;
}
};
int main()
{
const Date today { 2020, 10, 14 }; // const
today.day += 1; // compile error: can't modify member of const object
today.incrementDay(); // compile error: can't call member function that modifies member of const object
return 0;
}


常量对象不能调用非常量成员函数
你可能会惊讶地发现,这段代码也会导致编译错误:
#include <iostream>
struct Date
{
int year {};
int month {};
int day {};
void print()
{
std::cout << year << '/' << month << '/' << day;
}
};
int main()
{
const Date today { 2020, 10, 14 }; // const
today.print(); // compile error: can't call non-const member function
return 0;
}

尽管print()函数并未尝试修改成员变量,但我们对today.print()的调用仍构成const违规。这是因为print()成员函数本身未声明为const。编译器不允许对const对象调用非const成员函数。
常量成员函数
为解决上述问题,我们需要将print()定义为常量成员函数。常量成员函数const member function是保证不会修改对象或调用任何非常量成员函数(因其可能修改对象)的成员函数。
将print()设为常量成员函数很简单——只需在函数原型function prototype的参数列表之后、函数体之前添加const关键字:
#include <iostream>
struct Date
{
int year {};
int month {};
int day {};
void print() const // now a const member function
{
std::cout << year << '/' << month << '/' << day;
}
};
int main()
{
const Date today { 2020, 10, 14 }; // const
today.print(); // ok: const object can call const member function
return 0;
}

在上例中,print() 已被定义为 const 成员函数,这意味着我们可以将其用于 const 对象(例如 today)。
对于高级读者:
对于在类定义外部定义的成员函数,必须在类定义中的函数声明和类定义外部的函数定义中都使用 const 关键字。我们在第 15.2 课——类与头文件中展示了此示例。
构造函数不能声明为 const,因为它们需要初始化对象的成员,这要求对成员进行修改。构造函数相关内容详见第14.9课——构造函数入门。
若 const 成员函数试图修改数据成员或调用非 const 成员函数,将导致编译器报错。例如:
struct Date
{
int year {};
int month {};
int day {};
void incrementDay() const // made const
{
++day; // compile error: const function can't modify member
}
};
int main()
{
const Date today { 2020, 10, 14 }; // const
today.incrementDay();
return 0;
}

在此示例中,incrementDay() 被标记为 const 成员函数,但它试图修改 day。这将导致编译器报错。
const 成员函数可以像普通函数一样修改非成员变量(如局部变量和函数参数),并调用非成员函数。const 仅适用于成员变量。
关键要点:
常量成员函数不能:修改隐式对象,调用非常量成员函数。
常量成员函数可以:修改非隐式对象,调用常量成员函数,调用非成员函数。
常量成员函数可用于非常量对象
非常量对象可以调用常量成员函数:
#include <iostream>
struct Date
{
int year {};
int month {};
int day {};
void print() const // const
{
std::cout << year << '/' << month << '/' << day;
}
};
int main()
{
Date today { 2020, 10, 14 }; // non-const
today.print(); // ok: can call const member function on non-const object
return 0;
}

由于 const 成员函数既可用于 const 对象也可用于非 const 对象,若成员函数不修改对象状态,则应将其声明为 const。
最佳实践
不修改(且永远不会修改)对象状态的成员函数应声明为 const,以便其既可用于 const 对象也可用于非 const 对象。
请谨慎选择应用 const 的成员函数。一旦成员函数被声明为 const,该函数即可在 const 对象上调用。若后续移除成员函数的 const 声明,将导致所有在 const 对象上调用该成员函数的代码失效。
Const对象通过经过const参考
尽管实例格局的变量是一种方式来创建const的对象,更常见的方式得到常数目的是通过一个对象一个能通过const参考。
在教训12.5--通过由左值的参考,我们复盖的案情通过类型参数通常量基准而不是价值。 来回顾一下,通过一个类型参数通过值导致一个复制的类作出(这是缓慢)--大多数时候,我们不需要复制、参照的原始参数的工作只是现和避免制作副本。 我们通常使用参考const允许的功能接受const左值的论点和rvalue参数(例如文本和临时的对象)。
你能找出什么地方错了下列代码?
#include <iostream>
struct Date
{
int year {};
int month {};
int day {};
void print() // non-const
{
std::cout << year << '/' << month << '/' << day;
}
};
void doSomething(const Date& date)
{
date.print();
}
int main()
{
Date today { 2020, 10, 14 }; // non-const
today.print();
doSomething(today);
return 0;
}

答案在于,在 doSomething() 函数内部,date 被视为 const 对象(因为它是通过 const 引用传递的)。而使用这个 const date 对象调用非 const 成员函数 print() 时,由于无法对 const 对象调用非 const 成员函数,这将导致编译错误。
解决方法很简单:将 print() 声明为 const:
#include <iostream>
struct Date
{
int year {};
int month {};
int day {};
void print() const // now const
{
std::cout << year << '/' << month << '/' << day;
}
};
void doSomething(const Date& date)
{
date.print();
}
int main()
{
Date today { 2020, 10, 14 }; // non-const
today.print();
doSomething(today);
return 0;
}

现在在函数 doSomething() 中,const date 将能够成功调用 const 成员函数 print()。
成员函数的const与非const重载
最后,虽然这种做法并不常见,但确实可以重载成员函数,使其同时拥有const版本和非const版本。这是因为const限定符function’s signature被视为函数签名的一部分,因此仅在const属性const-ness上存在差异的两个函数会被视为不同的函数。
#include <iostream>
struct Something
{
void print()
{
std::cout << "non-const\n";
}
void print() const
{
std::cout << "const\n";
}
};
int main()
{
Something s1{};
s1.print(); // calls print()
const Something s2{};
s2.print(); // calls print() const
return 0;
}
这将输出:

当函数的返回值需要在常量性方面有所区别时,通常会采用常量版和非常量版函数重载的方式。这种情况相当罕见。

浙公网安备 33010602011771号