为什么QVector<TYPE*>的contains函数要求参数为非const类型的指针?

3
我在程序中使用QVector来存储指向对象(例如FYPE*)的指针。
    class TYPE {
        // ....
    };
    const TYPE *ptrToCst;
    QVector<TYPE*> qtVct;
    // ...
    if (qtVct.contains(ptrToCst)) { // Error!!!
        // ....
    }

编译器提示QVector::contains函数期望的参数是TYPE*而不是const TYPE*。使用const_cast操作可以解决这个问题。但是对我来说没有任何意义,因为contains方法不应该改变指针所指向的内容。而使用STL vector的等效代码按预期工作。
    std::vector<TYPE*>  stlVct;
    // ...
    if (std::find(stlVct.begin(), stlVct.end(), ptrToCst)) { // Ok
        // ...
    }

这种差异的原因是什么?STL是否特殊处理了容器来保存指针,以便std::find接受指向const对象的指针?我猜涉及到部分模板特化?

“显式模板实例化”与此完全无关,您是指“模板特化”吗? - David Rodríguez - dribeas
@DavidRodríguez-dribeas 对于术语的误用我很抱歉。你是对的,我确实是指“偏特化模板”。 - oyquan
2个回答

2
这实际上是一个关于const-correctness和一些非直观错误的有趣问题,但编译器在拒绝该代码时是正确的。
为什么它在Qt中不起作用?
编译器拒绝该代码是因为它会破坏代码的const-correctness。我不了解这个库,但我认为可以安全地假设contains的签名是QVector<T>::contains( T const & value )[1],在T == type*的情况下意味着:
QVector<type *>::contains( type * const & value )

现在的问题是,你试图传递一个类型为 type const * 的变量。问题在于编译器不知道 contains 内部做了什么,只知道它在接口中提供的承诺。 contains 承诺不改变指针,但没有关于指向的对象的说明。在签名中没有阻止实现从修改值中的 contains。考虑下面这个类似的例子:
void f( int * const & p ) { // I promise not to change p
   *p = 5;                  // ... but I can modify *p
}
int main() {
   const int k = 10;
   int const * p = &k;
   f( p );                  // Same problem as above: if allowed, f can modify k!!!
}

为什么它允许类似于std :: find的调用呢?

与std :: find不同之处在于,find是一个模板,其中不同的参数类型具有非常松散的耦合。标准库不会对接口执行类型检查,而是对模板的实现进行检查。如果参数类型不正确,则在实例化模板时将被拾取。

这意味着实现将类似于:

template <typename Iterator, typename T>
Iterator find( Iterator start, Iterator end, T const & value ) {
   while ( start != end && *start != value )
      ++start;
   return start;
}

迭代器的类型和值是完全无关的,除了模板内部强制实施的约束外,没有其他限制。这意味着调用将匹配参数作为Iterator == std::vector<type*>::iteratorT = type const *,并且签名将匹配。因为内部只使用value与*start进行比较,并且type *type const * const的比较是有效的(它将将前者转换为后者,然后进行比较[2]),所以代码可以完美编译。
如果模板有额外的限制,第二个参数的类型为Iterator::value_type const &(这可以通过SFINAE实现),那么find将无法编译,出现相同的错误。 [1]请注意声明中的排序选择:type const *,而不是const type *。它们对于编译器(和有经验的眼睛)来说完全相同,但是通过始终将const添加到右侧,可以轻松地确定正在定义的内容以及在contains的参数中T == type *const T & 不是const type *&[2]以同样的方式,您无法将type * const &绑定到type const *,也无法使用指针进行等效操作,type * const *无法从type const *转换,但是如果在指针和被指向的类型上都添加了const,则转换是可以的,因为它保证不会破坏const正确性。在比较type * == type const * const时执行该转换(它是安全的),其中左侧获得两个额外的consttype const * const。如果不清楚这为什么不会破坏const正确性,请留下评论,我将在此处提供一些代码。

1

显式模板实例化是针对具体类型完成的,您可以确定标准库供应商不知道您会编写TYPE。 除此之外,差异在于签名。 std::find是一个自由函数模板,类似于:

template <typename I, typename T>
find(I first, I last, T value)

因此,当您调用它时,编译器会生成find(std::vector<TYPE*>::iterator, std::vector<TYPE*>::iterator, const TYPE*)。由于所有的find只是比较,而且您可以毫无问题地比较const T*T*,所以一切都很好。

QVector<TYPE*>::contains则是一个类模板中的成员函数。因此,它的签名包含用于实例化模板的类型:

contains(TYPE*)

问题就在这里,因为您试图使用 const TYPE* 调用它——将 const T* 转换为 T* 是不合法的。

另外:find 返回一个迭代器,而不是布尔值。您的条件应该是 if (std::find(...) != that_vector.end())

直接回答“为什么 QVector::contains 期望其参数为非 const 类型指针”:因为您使用模板参数告诉它这样做了。


虽然你说得没错,但我个人认为这是Qt的一个严重缺陷。它绝不是const-correct的,当我试图在其周围编写const-correct代码时,这让我感到非常疯狂。最终,我删除了大部分的const声明,因为它根本行不通。 - arne
“find” 模板通过引用接受值参数:“T const&”,这很可能是 “QVector<T>::contains(T const&)” 的情况(我不了解 Qt,但我可以想象……) - David Rodríguez - dribeas
这是一个常见的令人困惑的问题:如果参数是const T&,其中T == type *,为什么它不接受const type *作为参数呢?毕竟当拼写出来时,它们是相同的!(不,它们不是,参数是type * const&而参数是type const *)尽管如此,可以同意这不是问题的根源。 - David Rodríguez - dribeas
@arne: 你在说“它绝对不是const正确”的时候是错误的,实际上它是“const正确”的。我们处理的是指针类型的const正确性,而不是指向的对象本身。除非你想为所有指针类型提供特殊化的容器,并且再为指向指针类型的指针提供特殊化……或者你可以打开函数接口,以便在与容器中存储的元素相关的参数和contains之间提供更松散的耦合(类似于std::find方法)。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:对于这种情况,我认输了。然而,在Qt的一些地方,我想知道为什么某些函数不接受“const”参数,这导致了我过早的陈述。 - arne
显示剩余2条评论

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