【问题标题】:Why is a vector of pointers not castable to a const vector of const pointers?为什么指针向量不能转换为 const 指针的 const 向量?
【发布时间】:2013-10-01 18:00:31
【问题描述】:

vector<char *> 类型不能转换为 const vector<const char*>。例如,以下给出编译错误:

#include <vector>

using namespace std;

void fn(const vector<const char*> cvcc)
{
}

int main()
{
    vector<char *> vc = vector<char *>(); 

    fn(vc);
}

我理解为什么vector&lt;char*&gt; 不能转换为vector&lt;const char*&gt; - 可以将const char * 类型的额外成员添加到向量中,然后它们可以作为非常量访问。但是,如果向量本身是 const,则不会发生这种情况。

我最好的猜测是这将是无害的,但编译器无法推断这是无害的。

如何解决这个问题?

这个问题是由 C++ FQA here 提出的。

【问题讨论】:

  • 改用迭代器。
  • vector&lt;T&gt;vector&lt;const T&gt; 是完全不同的类型。
  • 远离 FQA。在那段中(和其他大多数人一样),他完全是个白痴——通过举一个需要even more complexity 才能工作的示例来抱怨复杂的类型系统。
  • 类型安全等于好的代码等于避免程序员错误

标签: c++ casting constants


【解决方案1】:

通常,C++ 不允许您将 someclass&lt;T&gt; 转换为 someclass&lt;U&gt;,因为模板 someclass 可能专用于 UTU 的相关性无关紧要。这意味着实现和对象布局可能不同。

遗憾的是,没有办法告诉编译器在哪些情况下布局和/或行为没有改变并且应该接受强制转换。我想它对于std::shared_ptr 和其他用例可能非常有用,但这不会很快发生(AFAIK)。

【讨论】:

  • std::shared_ptrstd::X_pointer_cast。不过,它是一个非成员函数并明确添加了功能。
  • @DyP 是的,但是它返回一个新的shared_ptr,它不会通过更改类型而是指向相同的内存位置来转换现有实例。
  • @DanielFrey 有什么区别? A a; (B)b; 对某些可转换类型 AB 执行相同的操作。仅对于基本类型、指针和引用,强制转换不会产生一个对象(临时),而只是一个值。
  • @DyP 我很难表达,我不知道为什么。但对我来说,有转换(创建新对象)和强制转换,它们基本上是类型系统的一部分,但并不真正生成代码。嗯,我需要努力,但我现在很累:)
  • @Hi-Angel 此外,可能更相关的是,编译器可能找出布局兼容性,但它根本不这样做,因为标准不允许这样做.我在答案中的那句话不是指编译器,而是指you没有办法告诉编译器两个模板实例是兼容的。
【解决方案2】:
void fn(const vector<const char*>)

由于函数类型的顶级 const 限定符被删除,这(在调用站点)相当于:

void fn(vector<const char*>)

两者都请求传递的向量的副本,因为标准库容器遵循值语义。

您可以:

  • 通过fn({vc.begin(), vc.end()}) 调用它,请求显式转换
  • 将签名更改为,例如void fn(vector&lt;const char*&gt; const&amp;),即参考参考

如果你可以修改fn的签名,你可以改用GManNickGadvice and use iterators/a range:

#include <iostream>
template<typename ConstRaIt>
void fn(ConstRaIt begin, ConstRaIt end)
{
    for(; begin != end; ++begin)
    {
        std::cout << *begin << std::endl;
    }
}

#include <vector>
int main()
{
    char arr[] = "hello world";
    std::vector<char *> vc;
    for(char& c : arr) vc.push_back(&c);

    fn(begin(vc), end(vc));
}

这给出了漂亮的输出

你好世界 你好世界 世界 世界 世界 世界 世界 世界 rld ld d

基本问题是传递标准库容器。如果您只需要不断地访问数据,则不需要知道实际的容器类型,而可以使用模板来代替。这消除了fn 与调用者使用的容器类型的耦合。

如您所见,允许通过std::vector&lt;const T*&gt;&amp; 访问std::vector&lt;T*&gt; 是个坏主意。但是如果你不需要修改容器,你可以使用一个范围来代替。

如果函数fn 不应或不能是模板,您仍然可以传递const char* 的范围而不是const char 的向量。这适用于任何保证连续存储的容器,例如原始数组、std::arrays、std::vectors 和 std::strings。

【讨论】:

    【解决方案3】:

    要解决此问题,您可以实现自己的模板包装器:

    template <typename T> class const_ptr_vector;
    
    template <typename T> class const_ptr_vector<T *> {
        const std::vector<T *> &v_;
    public:
        const_ptr_vector (const std::vector<T *> &v) : v_(v) {}
        typedef const T * value_type;
        //...
        value_type operator [] (int i) const { return v_[i]; }
        //...
        operator std::vector<const T *> () const {
            return std::vector<const T *>(v_.begin(), v_.end());
        }
        //...
    };
    

    这个想法是包装器为引用的向量提供了一个接口,但返回值总是const T *。由于引用的向量是const,因此包装器提供的所有接口也应该是const,如[] 运算符所示。这包括将由包装器提供的迭代器。包装迭代器只包含一个指向被引用向量的迭代器,但所有产生指针值的操作都是const T *

    此解决方法的目标不是提供可以传递给需要 const std::vector&lt;const T *&gt; &amp; 的函数的东西,而是提供不同的类型以用于提供此类向量的类型安全的函数。

    void fn(const_ptr_vector<char *> cvcc)
    {
    }
    

    虽然您无法将其传递给需要 const std::vector&lt;const T *&gt; &amp; 的函数,但您可以实现一个转换运算符,该运算符将返回使用来自底层向量的指针值初始化的 std::vector&lt;const T *&gt; 的副本。上面的例子也说明了这一点。

    【讨论】:

    • 很好,但是您仍然不能将此包装器传递给需要向量 的函数。你需要像 C# 的 IEnumerable 这样的东西并在任何地方使用它。
    • @jdv-JandeVaan:我并没有试图解决转换问题,而是通过不同的类型提供一种解决方法来代替它使用。但是,我已经使用一个转换运算符更新了答案,该运算符创建了一个到 std::vector&lt;const T *&gt; 的副本。
    【解决方案4】:

    正如 FQA 所暗示的,这是 C++ 中的一个根本缺陷。

    看来你可以通过一些显式转换来做你想做的事:

    vector<char*> vc = vector<char *>(); 
    vector<const char*>* vcc = reinterpret_cast<vector<const char*>*>(&vc);
    fn(*vcc);
    

    这会调用未定义的行为,并且不能保证有效;但是,我几乎可以肯定它会在关闭严格别名的 gcc 中工作 (-fno-strict-aliasing)。无论如何,这只能作为临时破解;你应该只复制向量以保证你想做的事情。

    std::copy(vc.begin(), vc.end(), std::back_inserter(vcc));
    

    从性能的角度来看,这也是可以的,因为fn 在被调用时会复制它的参数。

    【讨论】:

    • 这不是基本缺陷,而是基本特征。
    • 反正我妈妈总是这么告诉我的。
    • 我考虑在非惯用 C++ 容器上使用 reinterpret_cast。说得客气一点。
    • 这是一个基本的妥协,以避免使类型系统更加复杂。毕竟,这是在 FQA 部分声称类型系统太复杂了。妥协是有道理的,FQA 没有。
    • 就存储而言,所有指针类型基本相同,因此我认为这与 C 样式转换没有什么不同。它可能不安全,但我敢打赌它在 99% 的时间里都有效。如果 C++ 有列表解析,reinterpret_cast 选项就不会那么吸引人了,但是它需要相当多的代码来构造一个容器。
    猜你喜欢
    • 2017-05-15
    • 2015-09-10
    • 2011-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-23
    相关资源
    最近更新 更多