Qt - QList的常量正确性

6

QList<T *>不容易实现const-correct。考虑下面这个函数:

void f(QList<T *> list)
{
    list[0]->constFunction();
}

我可以将 f 改为
void f(QList<const T *> list)

但是我就做不了了。

f(QList<T *>());    //Compile error

现在不再可以隐式地将QList<T *>转换为QList<const T *>,因为编译器无法进行转换。但是,可以使用显式的reinterpret-cast将QList转换为如下形式:

template <typename T> inline QList<const T *> &constList(const QList<T *> &list)
{
    return (QList<const T *> &)list;
}

这使我能够使用constList模板函数将任何QList<T *>转换为QList<const T *>,例如:
f(constList(QList<T *>()));

看起来这样做没问题,但实际上这样做是否安全呢?

2个回答

8
您考虑的铸造函数,…
template< class T >
inline QList<T const*>& constList( QList<T*> const& list )
{
    return reinterpret_cast< QList<T const*>& >(
        const_cast< QList<T*>& >( list )
        );
}

...可能是实用的(可能是因为 QList 不会根据元素类型的 const 属性改变其对象表示),但它可能会破坏 const 正确性

首先,因为去掉列表本身的 const 属性并不符合 const 正确性:它允许您更改最初的 const 列表。

但即使删除了该形式参数 const ,例如...

template< class T >
inline QList<T const*>& constList( QList<T*>& list )
{
    return reinterpret_cast< QList<T const*>& >(
        list
        );
}

仍然存在一个const正确性问题。

原因是列表构成了额外的间接级别,并且使用您的函数后本身不是const。因此,在使用您的函数获取到指向所谓指向const元素的指针的列表的引用之后,您可以在该列表中存储指向实际上是const的内容的指针。然后,您可以使用原始列表来修改那个真正的const项,出错了

这也是为什么没有从T **隐式转换为T const **隐式转换的原因。

如果您已经有一个指向对象的指针的const列表,则可以避免这些问题,并将指向的对象设置为const

template< class T >
inline QList<T const*> const& constList( QList<T*> const& list )
{
    return reinterpret_cast< QList<T const*> const& >(
        list
        );
}

严格来说,reinterpret_cast仍然可能是一个潜在的问题,但是任何专门处理QList元素constness表示的人都应该得到他们想要的结果。 :-)

祝好!


谢谢Alf - 好的,但是当移除constList函数的list参数的const并将f更改为void f(const QList<const T *> &list)时,这样做是否安全? - emkey08
1
@emkey08:我不太确定你的意思,但你可以简单地测试是否存在从T cvA* cvA到T cvB cvB*(其中“cv”表示const-volatile限定符)的隐式转换。如果存在隐式转换,则一切正常。如果没有,则不好。祝好运。 - Cheers and hth. - Alf
你已经指出了在我打字的同时更改constList函数的可能性。这应该是更好的方法。谢谢! - emkey08

3
常量正确性的概念比较薄弱,你发现的只是其中之一。常量性只捕捉到一个语义位(改变/不改变),而且做得不好,语法成本很高,有时甚至需要代码复制。
这个概念的一个问题是它不能很好地通过组合来扩展:例如,我可能希望将一个函数传递给一个非const对象的const向量,或者一个const对象的非const向量,或者一个const对象的const向量,并且正确使用语法是昂贵的...只需考虑标准C++如何被迫引入const_iterator,因为它当然不同于const iterator。
多年来,我从狂热地在每个可能的地方使用常量正确性的立场转变为只在被迫使用时使用它的相反立场。经验告诉我,不过分关注常量正确性的代码更加简洁,而且常量正确性机制从未真正捕捉到(至少对我来说)任何逻辑错误,只捕捉到与常量正确性机制本身有关的错误。不幸的是,常量正确性是C++中必须使用的东西,因为C++规则如此规定,没有办法避免使用它。
我知道我的帖子会被踩,但我的建议是对你所读到的关于C++这个主题的任何东西都要保持批判性的眼光;保持头脑清醒,客观地评判。仅仅因为某些事情是真的(例如常量正确性有助于程序员)并不意味着它真的是真的。
无论签署书籍的人有多大名气,也要记住,如果一个立场需要让整个世界都错了,那么至少应该持怀疑态度:大多数语言,甚至是在C++之后出生的语言,都没有实现这个概念。

1
当使用任何第三方C++代码/库/框架(例如Qt)时,通常您没有其他选择,只能使用const正确性,至少对于与第三方代码交互的代码部分。而且我真的不会在同一项目中混合使用const-correct和non-const-correct代码。 - emkey08
在我看来,常量正确性并不是那么关于捕捉逻辑错误。它更多地是关于使代码更易于理解,因此在某种程度上也有助于避免首先发生这些错误。代码变得更容易理解,因为常量性约束了代码中可能发生的事情,因此需要考虑的内容要少得多才能理解代码。干杯! - Cheers and hth. - Alf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接