【问题标题】:vector and const向量和常量
【发布时间】:2010-01-20 14:59:03
【问题描述】:

考虑一下

 void f(vector<const T*>& p)
 {
 }
 int main()
 { 
  vector<T*> nonConstVec;
  f(nonConstVec);
 }

以下内容无法编译。问题是 vector&lt;T*&gt; 无法转换为 vector &lt;const T*&gt; ,这对我来说似乎不合逻辑,因为存在从 T*const T* 的隐式转换。这是为什么呢?

vector&lt;const T*&gt; 也不能转换为 vector &lt;T*&gt;,但这是可以预期的,因为 const T* 不能隐式转换为 T*

【问题讨论】:

    标签: c++ stl vector constants


    【解决方案1】:

    我在您的代码中添加了几行代码。这足以说明为什么不允许这样做:

    void f(vector<const T*>& p)
     {
        static const T ct;
        p.push_back(&ct); // adds a const T* to nonConstVec !
     }
     int main()
     { 
      vector<T*> nonConstVec;
      f(nonConstVec);
      nonConstVec.back()->nonConstFunction();
     }
    

    【讨论】:

    • 太好了,我从没想过这个
    • 如果这是原因,转换为const vector&lt;const T*&gt; 应该可以工作
    • @marcin:这在理论上是安全的,但如果没有std::vector 的详细知识,编译器无法确定这一点。此外,要使其合法,需要对转换顺序规则进行复杂的更改。将 Foo&lt;T*&gt; 转换为 const Foo&lt;const T*&gt;template &lt;class T&gt; class Foo 有什么要求?请记住,在这种情况下,Foo 可以是智能指针模板或其他任何东西,不一定是容器。
    • 网上的很多解释只是指出vector&lt;T *&gt;vector&lt;T const *&gt;是不相关的类型。这并不能解释为什么不提供转换(毕竟这是C++,我们有converting constructors和可重载的conversion operator)。这是为什么添加转换是一件坏事的最简单、最清楚的例子。
    【解决方案2】:

    vector&lt;T&gt;vector&lt;const T&gt; 是不相关的类型。 T 可以转换为 const T 的事实在这里并不意味着什么。

    您必须从类型系统的角度来考虑它。实例化的vector&lt;int&gt;vector&lt;const int&gt; 没有任何共同之处。

    【讨论】:

    • 很好的答案。这为我明确了 什么 是非法的,以及为什么从编译器的角度来看,这是不允许的。其他答案解释了为什么这可能是一个坏主意,这很好知道,但是很多法律代码是一个坏主意。
    【解决方案3】:

    可能值得说明为什么执行您想要的转换会违反 const 正确性:

    #include <vector>
    const int a = 1;
    
    void addConst(std::vector<const int *> &v) {
        v.push_back(&a); // this is OK, adding a const int* to a vector of same
    }
    
    int main() {
        std::vector<int *> w;
        int b = 2;
        w.push_back(&b);  // this is OK, adding an int* to a vector of same
        *(w.back()) = 3;  // this is OK, assigning through an int*
        addConst(w);      // you want this to be OK, but it isn't...
        *(w.back()) = 3;  // ...because it would make this const-unsafe.
    }
    

    问题是vector&lt;int*&gt;.push_back 采用指向非常量的指针(从现在开始我将其称为“非常量指针”)。这意味着,它可能会修改其参数的指针。特别是在向量的情况下,它可能会将指针交还给修改它的其他人。因此,您不能将 const 指针传递给 w 的 push_back 函数,即使模板系统支持它(它不支持),您想要的转换也是不安全的。 const-safety 的目的是阻止您将 const 指针传递给接受非 const 指针的函数,这就是它的工作方式。 C++ 要求您明确说明是否要执行不安全的操作,因此转换肯定不能是隐式的。事实上,由于模板的工作方式,根本不可能(见下文)。

    我认为 C++ 原则上可以通过允许从 vector&lt;T*&gt;&amp; 转换到 const vector&lt;const T*&gt;&amp; 来保持 const-safety,就像 int **const int *const * 是安全的一样。但这是因为 vector 的定义方式:对于其他模板,它不一定是 const 安全的。

    同样,理论上它可以允许显式转换。事实上,它确实允许显式转换,但仅限于对象,而不是引用;-)

    std::vector<const int*> x(w.begin(), w.end()); // conversion
    

    它不能做引用的原因是模板系统不能支持它。如果允许转换,将破坏的另一个示例:

    template<typename T> 
    struct Foo {
        void Bar(T &);
    };
    
    template<>
    struct Foo<const int *> {
        void Baz(int *);
    };
    

    现在,Foo&lt;int*&gt; 没有 Baz 功能。到底如何将Foo&lt;int*&gt; 的指针或引用转换为Foo&lt;const int*&gt; 的指针或引用?

    Foo<int *> f;
    Foo<const int *> &g = f; // Not allowed, but suppose it was
    int a;
    g.Baz(&a); // Um. What happens? Calls Baz on the object f?
    

    【讨论】:

      【解决方案4】:

      这样想:

      你有两个这样的类:

      class V  { T*       t;};
      class VC { T const* t;};
      

      您希望这两个类可以自动转换吗?
      这基本上就是模板类。每个变体都是一种全新的类型。

      因此vector和vector是完全不同的类型。

      我的第一个问题是你真的要存储指针吗?

      如果是,我建议查看 boost::ptr_container。这会保存指针并在向量被销毁时删除它们。但更重要的是,它将包含的指针视为普通的 std:vector 处理其包含的对象。因此,通过将向量设为 const,您只能以 const 访问其成员

      void function(boost::ptr_vector<T> const& x)
      {
           x.push_back(new T);  // Fail x is const.
           x[4].plop();         // Will only work if plop() is a const member method.
      }
      

      如果您不需要存储指针,则将对象(不是指针)存储在容器中。

      void function(std::vector<T> const& x)
      {
           x.push_back(T());    // Fail x is const.
           x[4].plop();         // Will only work if plop() is a const member method.
      }
      

      【讨论】:

        【解决方案5】:

        其他人已经给出了您提供的代码无法编译的原因,但我对如何处理它有不同的答案。我不相信有任何方法可以教编译器如何自动转换两者(因为这将涉及更改std::vector 的定义)。解决这种烦恼的唯一方法是进行显式转换。

        转换为完全不同的向量是不令人满意的(浪费内存和周期来处理应该完全相同的东西)。我建议如下:

        #include <vector>
        #include <iostream>
        
        using namespace std;
        
        typedef int T;
        
        T a = 1;
        T b = 2;
        
        void f(vector<const T*>& p)
        {
            for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) {
                cout << **iter << endl;
            }
        }
        vector<const T*>& constify(vector<T*>& v)
        {
          // Compiler doesn't know how to automatically convert
          // std::vector<T*> to std::vector<T const*> because the way
          // the template system works means that in theory the two may
          // be specialised differently.  This is an explicit conversion.
          return reinterpret_cast<vector<const T*>&>(v);
        }
        int main()
        {
          vector<T*> nonConstVec;
          nonConstVec.push_back(&a);
          nonConstVec.push_back(&b);
          f(constify(nonConstVec));
        }
        

        我使用reinterpret_cast 声明这两件事是相同的。你应该在使用后感觉很脏,但是如果你把它单独放在一个函数中并为关注你的人发表评论,那么请洗刷并尝试继续你的路,尽管如此您将永远(正确地)担心有人从您身下拉出地面。

        【讨论】:

          【解决方案6】:

          正如其他人所说,转换不适用于模板参数。换句话说,

          vector<T>
          

          ...和:

          vector<const T>
          

          ...是完全不同的类型。

          如果您尝试在 f() 不修改向量的内容方面实现 const 正确性,这可能更符合您正在寻找的内容:

          void f(vector<T>::const_iterator begin, vector<T>::const_iterator end)
          {
            for( ; begin != end; ++begin )
            {
              // do something with *begin
            }
          }
          
          int main()
          {
            vector<T> nonConstVec;
            f(nonConstVec.begin(), nonConstVec.end());
          }
          

          【讨论】:

            【解决方案7】:

            除了其他答案,值得阅读 C++ FQA Lite,其中从关键的 POV 讨论了这个(以及许多其他 C++ 特性): http://yosefk.com/c++fqa/const.html#fqa-18.1

            【讨论】:

            • 我同意它值得一读。尽管 FQA 风格激进,但它是巩固 C++ 知识的好方法
            【解决方案8】:

            这就是模板的工作方式——没有对模板参数应用任何转换,因此这两个向量是完全不同的类型。

            【讨论】:

            • 比如何将我的vector传递给获取vector的函数?我当然不想更改函数的签名,因为它就像注释说它不会修改传递的对象。
            • 好吧,恐怕你必须改变它。如果你想说你没有改变向量,那么你想要 f( const vector & );
            • f(const &) 表示向量(包含指针)本身不会改变,但指针指向的对象可以改变。这不是我想要的。
            • 这是将算法与其底层类型分开的好处。 @John 的回答证明了这一点。
            • 我认为你只需要“吸干它”。正如其他人所表明的那样,允许你的方式会破坏其他地方的 const 正确性。您可以通过其他方式将风险降至最低,例如,如果您要依次使用每个指针,请将其设为接受 const 指针的单独函数。
            【解决方案9】:

            vector&lt;const T*&gt;vector&lt;T*&gt; 是完全不同的类型。即使您在main() 中写入const T*,您的代码也不会编译。您需要在 main 中提供专业化。

            以下编译:

             #include<vector>
             using namespace std;
            
             template<typename T>
             void f(vector<const T*>& p)
             {
             }
             int main()
             { 
                 vector<const int*> nonConstVec;
                 f(nonConstVec);
             }
            

            【讨论】:

              【解决方案10】:

              这样的模板有点奇怪。从 T 到 U 的隐式转换并不意味着从 XXX 到 XXX 的隐式转换。它可以实现,但需要在模板代码中进行大量额外工作才能实现,而且我怀疑在设计std::vector 时这些技术都是已知的(更准确地说,我很漂亮确保他们不为人所知)。

              编辑:像这样的问题是使用迭代器背后的动机的一部分。即使container of X 不能隐式转换为container of const Xcontainer&lt;X&gt;::iterator 也可以隐式转换为container&lt;X&gt;::const_iterator

              如果你替换你的:

              void f(vector<const T*>& p) {}
              

              与:

              template <class const_iter>
              void f(const_iter b, const_iter e) {}
              

              然后:

              int main() { 
                  vector<T*> nonConstVec;
                  f(nonConstVec.begin(), nonConstVec.end());
                  return 0;
              }
              

              会很好 - 也会:

              vector<T const *> constVec;
              f(constVec.begin(), constVec.end());
              

              【讨论】:

                猜你喜欢
                • 2020-10-23
                • 2018-09-23
                • 1970-01-01
                • 1970-01-01
                • 2022-01-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-09-09
                相关资源
                最近更新 更多