让我们采用以下源代码:
const ushort *QString::utf16() const
{
if (IS_RAW_DATA(d)) {
// ensure '\0'-termination for ::fromRawData strings
const_cast<QString*>(this)->reallocData(uint(d->size) + 1u);
}
return d->data();
}
ReallocData()修改d指针类成员d,见https://code.woboq.org/qt5/qtbase/src/corelib/tools/qstring.cpp.html#_ZN7QString11reallocDataEjb。如果QString对象是const怎么办?根据https://en.cppreference.com/w/cpp/language/const_cast修改const对象抛弃常量是UB:
struct type {
int i;
type(): i(3) {}
void f(int v) const {
const_cast<type*>(this)->i = v; // OK as long as the type object isn't const
}
};
type t; // if this was const type t, then t.f(4) would be undefined behavior
t.f(4);
我们在这段特定的代码(QString::utf16())中有UB吗?
如果调用此方法的QString
对象是const限定的,则这确实是一个UB:
QString const str{"whatever"};
str.utf16();
请注意,这里的重要部分是对象是const限定的,而不是方法是const限定的。
当(并且仅当)有问题的对象最初创建时,这是未定义的行为const
:
QString const sc("whatever");
sc.utf16(); // undefined behaviour!
QString s("whatever");
QString const& scr = s;
scr.utf16(); // NOT undefined behaviour!
尽管如此,抛弃一致性是一个非常糟糕的主意,因为在函数中,你永远不知道所讨论的对象是一个真正的常量对象还是仅仅是一个指针/引用,它最初创建的对象是非常量的,所以总是存在UB的内在危险!
最好的只是没有函数const:
const ushort* QString::utf16()
{
//...
}
根据情况,用户将被迫创建一个非常量副本,即使原始对象实际上是非常量的,但这总是比冒UB的风险要好!