【问题标题】:Can I use const in vectors to allow adding elements, but not modifications to the already added?我可以在向量中使用 const 来允许添加元素,但不能修改已添加的元素吗?
【发布时间】:2011-02-15 02:31:33
【问题描述】:

this answer 上的 cmets 让我开始思考 constness 和排序的问题。我玩了一下,将我的问题减少到以下代码:

#include <vector>

int main() {
    std::vector <const int> v;  
}

不会编译 - 你不能创建一个 const int 的向量。显然,我应该知道这一点(并且在智力上我做到了),但我以前从来不需要创造这样的东西。但是,它对我来说似乎是一个有用的构造,我想知道是否有任何方法可以解决这个问题 - 我想将东西添加到向量(或其他),但一旦添加就不应更改它们。强>

可能有一些令人尴尬的简单解决方案,但这是我以前从未考虑过的。 我可能不应该提到排序(我可能会问另一个问题,请参阅this 了解提问的困难)。我真正的基本用例是这样的:

vector <const int> v;     // ok (i.e. I want it to be OK)
v.push_back( 42 );        // ok
int n = v[0];             // ok
v[0] = 1;                 // not allowed

【问题讨论】:

  • 对于那些想知道为什么上述不能保证工作的人是容器中的元素必须是可复制构造和可复制分配的。后者不适用于 const 对象。 (请注意,上面的内容在 VS 中可以使用,因为在这种情况下它不使用可复制分配的要求,但是 GCC 会这样做并且它会失败。不过,两种实现都是正确的。)
  • 我不确定我是否真的理解这个问题。向量中使用的类型必须是Assignable,因此即使您使用代理对象尝试在某个级别为包装对象添加常量,op[]at 或derefenced 的迭代器可以从另一个外部创建的代理对象分配,因此代理的实现必须很容易被破坏。我错过了什么吗?
  • @GMan VS 真的允许这样做吗?如果确实如此,我会认为它会坏掉。你说两者都是正确的依据是什么?
  • @Donal Fellows:即使在 C++03 中,您也可以使用 const 类型执行就地销毁然后放置复制构造,因此构建const 类型的容器在技术上是可行的。
  • @Neil:确实,VS(真的是 Dinkumware)在添加元素和复制元素时会使用复制构造函数。相反,GCC 使用赋值来分配值。我说两者都是正确的原因是实现可以做任何他们想做的事情。然而,有人可能会争辩说 VS 允许这是一种“扩展”,因为 const 类型不满足容器要求,而且它恰好工作的事实仅仅是一个实现细节。

标签: c++ stl constants


【解决方案1】:

嗯,在 C++0x 中你可以...

在 C++03 中,有一段 23.1[lib.containers.requirements]/3,它说

这些组件中存储的对象类型必须满足CopyConstructible类型(20.1.3)的要求,以及Assignable类型的附加要求。

这是目前阻止您使用 const int 作为 std::vector 的类型参数的原因。

但是,在 C++0x 中,缺少此段落,而是要求 TDestructible 并且对 T 的附加要求是针对每个表达式指定的,例如v = u on std::vector 仅在 TMoveConstructibleMoveAssignable 时有效。

如果我正确地解释了这些要求,应该可以实例化std::vector&lt;const int&gt;,你只会丢失它的一些功能(我猜这就是你想要的)。您可以通过将一对迭代器传递给构造函数来填充它。我认为emplace_back() 应该也可以工作,尽管我没有找到对T 的明确要求。

您仍然无法就地对向量进行排序。

【讨论】:

  • +1 听起来它可能会工作 - 虽然在 g++ 4.4.1(我最新的)和-std=c++0x
  • 接受这个,因为它表明我想要的东西将来可能会出现——我喜欢生活在希望中!
【解决方案2】:

您放入标准容器中的类型必须是可复制和可分配的。 auto_ptr 造成这么多麻烦的原因正是因为它不遵循正常的复制和赋值语义。自然,const 的任何内容都不可分配。因此,您不能在标准容器中粘贴 const 任何东西。如果元素不是const,那么您能够更改它。

我认为可能的最接近的解决方案是使用某种间接方式。因此,您可以有一个指向 const 的指针,或者您可以有一个对象,该对象包含您想要的值,但该值不能在对象内更改(就像您在 Java 中使用 Integer 一样)。

让特定索引处的元素不可更改与标准容器的工作方式背道而驰。您也许可以构建自己的以这种方式工作的方法,但标准的却不行。除非您能够设法将它们的初始化适应 {a, b, c} 初始化语法,否则基于数组的任何一个都不会起作用,因为一旦创建了 const 的数组,您就无法更改它。因此,无论您做什么,vector 类都不太可能与 const 元素一起使用。

在没有某种间接方式的容器中拥有const 并不能很好地工作。您基本上要求制作整个容器const - 如果您从已经初始化的容器复制到它,您可以这样做,但您不能真正拥有一个容器 - 当然不是标准容器 - 它包含没有一些常量一种间接的方式。

编辑:如果您想要做的是大部分情况下保持容器不变,但仍然能够在代码中的某些地方更改它,那么在大多数地方使用 const ref 并且然后提供需要能够更改容器直接访问或非常量引用的代码将使这成为可能。

所以,在大多数地方使用const vector&lt;int&gt;&amp;,然后在需要更改容器的地方使用vector&lt;int&gt;&amp;,或者让那部分代码直接访问容器。这样一来,它几乎是不可更改的,但您可以随时更改。

另一方面,如果您希望能够几乎始终能够更改容器中的内容但不更改特定元素,那么我建议在容器周围放置一个包装器类。在vector 的情况下,包装它并使下标运算符返回一个常量引用而不是一个非常量引用——要么是那个引用,要么是一个副本。因此,假设您创建了一个模板化版本,您的下标运算符将如下所示:

const T& operator[](size_t i) const
{
    return _container[i];
}

这样,您可以更新容器本身,但不能更改它的单个元素。只要您将所有函数声明为内联,使用包装器就不会对性能造成太大影响(如果有的话)。

【讨论】:

  • 我已经编辑了我的问题,希望不会改变基本含义。排序的概念让我提出了问题,但我感兴趣的问题更基础一些。
【解决方案3】:

你不能创建一个由 const int 组成的向量,即使你可以,它也毫无用处。如果我删除第二个 int,那么从那里开始的所有内容都会向下移动一个 - 阅读:修改 - 使得无法保证 v[5] 在两个不同的场合具有相同的值。

除此之外,一个 const 在声明后不能被分配,除非抛弃它。如果你想这样做,你为什么要首先使用 const?

【讨论】:

  • 我的用例是我希望常量值向量以一种方式排序,然后我想更改顺序,但不更改值。
  • 您可能需要考虑将向量设为 const,然后在需要修改它时丢弃该常量(并且知道您不会弄乱这些值)。或者将您自己的类包裹在向量周围,并自己调节访问,因为您所说的不再是 STL 意义上的“向量”——它的一半功能已经消失。跨度>
  • 抛弃 constness 最有可能让我留在 IB 或 UB 领域。我同意你的观点,不过,我所问的并不是真正的向量。
  • @Neil:有时更改顺序会更改值。例如,如果一个对象包含一个指向自身的指针,那么移动该对象也会修改它。
【解决方案4】:

您将需要编写自己的课程。您当然可以使用 std::vector 作为您的内部实现。然后只需实现 const 接口和您需要的那些少数非 const 函数。

【讨论】:

    【解决方案5】:

    虽然这不能满足您的所有要求(能够排序),但请尝试使用常量向量:

    int values[] = {1, 3, 5, 2, 4, 6};
    const std::vector<int> IDs(values, values + sizeof(values));
    

    不过,您可能想使用std::list。使用列表,值不需要更改,只需更改它们的链接。排序是通过改变链接的顺序来完成的。

    您可能需要花费一些脑力并自己编写。 :-(

    【讨论】:

    • 不幸的是,vectorlist 的性能如此不同,因此很少能将其用于另一个。
    • 虽然 OP 的问题中没有提到性能,但有时质量和稳健性比性能更重要。
    【解决方案6】:

    我会将所有 const 对象放在一个标准数组中。
    然后使用指向数组的指针向量。
    一个小型实用程序类,旨在帮助您不必取消引用对象和干草。

    #include <vector>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    
    
    class XPointer
    {
        public:
            XPointer(int const& data)
                : m_data(&data)
            {}
    
        operator int const&() const
        {
            return *m_data;
        }
    
        private:
            int const*  m_data;
    
    };
    
    int const               data[]    =  { 15, 17, 22, 100, 3, 4};
    
    std::vector<XPointer>   sorted(data,data+6);
    
    
    int main()
    {
        std::sort(sorted.begin(), sorted.end());
        std::copy(sorted.begin(), sorted.end(), std::ostream_iterator<int>(std::cout, ", "));
        int x   = sorted[1];
    }
    

    【讨论】:

      【解决方案7】:

      我支持诺亚:用一个只公开您想要允许的内容的类包装向量。

      如果您不需要将对象动态添加到向量中,请考虑std::tr1::array

      【讨论】:

        【解决方案8】:

        如果在这种情况下 const 对您很重要,我认为您可能希望一直使用不可变类型。从概念上讲,您将拥有一个固定大小的 const 数组 const ints。任何时候您需要更改它(例如添加或删除元素,或排序),您都需要使用执行的操作制作数组的副本并改用它。 虽然这在函数式语言中很自然,但在 C++ 中似乎不太“正确”。例如,获得排序的有效实现可能会很棘手 - 但你没有说你的性能要求是什么。 无论您是否认为从性能/自定义代码的角度来看这条路线是否值得,我都相信这是正确的方法。

        之后,通过非常量指针/智能指针保存值可能是最好的(但当然有它自己的开销)。

        【讨论】:

          【解决方案9】:

          我一直在思考这个问题,您的要求似乎已关闭。

          您不想将不可变的值添加到您的向量中:

          std::vector<const int> vec = /**/;
          std::vector<const int>::const_iterator first = vec.begin();
          
          std::sort(vec.begin(), vec.end());
          
          assert(*vec.begin() == *first); // false, even though `const int`
          

          您真正想要的是您的向量以可修改的顺序保存一个恒定的值集合,即使它有效,也无法用 std::vector&lt;const int&gt; 语法表示。

          恐怕这是一项非常具体的任务,需要专门的课程。

          【讨论】:

            【解决方案10】:

            确实可赋值是向量元素类型的标准要求之一,const int 是不可赋值的。但是,我希望在经过深思熟虑的实现中,只有当代码明确依赖分配时,编译才会失败。例如,对于std::vector,这将是inserterase

            实际上,在许多实现中,即使您不使用这些方法,编译也会失败。例如,Comeau 无法编译普通的 std::vector&lt;const int&gt; a;,因为 std::allocator 的相应特化无法编译。它报告std::vector 本身没有直接问题。

            我相信这是一个有效的问题。如果类型参数是 const 限定的,则库提供的实现 std::allocator 应该会失败。 (我想知道是否可以对 std::allocator 进行自定义实现以强制整个编译。)(知道 VS 如何编译它也会很有趣)同样,Comeau std::vector&lt;const int&gt; 无法编译出于同样的原因,std::allocator&lt;const int&gt; 无法编译,并且根据std::allocator 的规范,它必须编译失败。

            当然,在任何情况下,任何实现都有权编译 std::vector&lt;const int&gt; 失败,因为语言规范允许编译失败。

            【讨论】:

              【解决方案11】:

              仅使用非专业化的vector,这是无法做到的。排序是通过赋值来完成的。所以使这成为可能的相同代码:

              sort(v.begin(), v.end());

              ...也使这成为可能:

              v[1] = 123;

              【讨论】:

                【解决方案12】:

                您可以从 std::vector 派生一个类 const_vector,它重载任何返回引用的方法,并使其返回一个 const 引用。要进行排序,请返回 std::vector。

                【讨论】:

                • 我宁愿包装一个std::vector&lt;&gt;delete,而不是派生自堆的派生对象可能会导致基类CTor不被调用。包装器不会发生这种情况。然而,包装器也可以实现转换运算符。
                • 恕我直言,从 STL 容器派生总是一个坏主意。
                • @mxp,删除派生对象只有在删除向下转换的指针时才会成为问题。删除与 new 分配的相同类型的指针总是安全的。
                • @Matthieu,确实 STL 容器并非专门用于派生;特别是他们没有虚拟析构函数。但是我认为这些问题往往被夸大了。
                • @Mark:我理解您的观点,无论如何,这些容器并不经常是新的。但是我们在这里可以在继承和组合之间进行选择,并且根据经验,每当我在两种关系之间进行选择时,我都会选择较松散的一种。当然,由于 C++ 没有委托的概念,这意味着编写大量转发函数,但这不会花费太多,如果我愿意的话,我可以在闲暇时将 vector 更改为 deque
                【解决方案13】:

                常量对象的std::vector 可能由于Assignable 的要求而编译失败,因为常量对象不能被分配。移动分配也是如此。这也是我在使用基于矢量的地图时经常遇到的问题,例如 boost flat_map 或 Loki AssocVector。因为它有内部实现 std::vector&lt;std::pair&lt;const Key,Value&gt; &gt; 。 因此,几乎不可能遵循地图的 const key 要求,这对于任何基于节点的地图都可以轻松实现。

                但是可以看出,std::vector&lt;const T&gt; 是否意味着向量应该存储一个const T 类型的对象,或者它只需要在访问时返回一个非可变接口。 在这种情况下,std::vector&lt;const T&gt; 的实现是可能的,它遵循可分配/移动可分配要求,因为它存储类型为 T 而不是 const T 的对象。标准 typedef 和分配器类型需要稍作修改以支持标准要求。尽管支持 vector_mapflat_map,但可能需要对 std::pair 接口进行相当大的更改,因为它直接公开成员变量 first 和 second .

                【讨论】:

                • 我记得为了好玩而尝试实现 flat_map 并最终放弃,因为 pair 太不切实际了:你需要一个 std::pair&lt;Key,Value&gt; 内部但你想公开一个 std::pair&lt;const Key,Value&gt;在工作参考上需要一些reintepret_cast(呃)。我不知道他们为什么选择在地图界面中公开这对细节,但我真希望那天他们没有:(
                【解决方案14】:

                编译失败,因为push_back()(例如)基本上是

                underlying_array[size()] = passed_value;
                

                其中两个操作数都是T&amp;。如果Tconst X,那就不行了。

                拥有 const 元素在原则上似乎是正确的,但在实践中它是不自然的,并且规范并没有说应该支持它,所以它不存在。至少不在标准库中(因为那样的话,它将在向量中)。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-04-01
                  • 1970-01-01
                  • 2023-01-09
                  • 2020-09-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-12-19
                  相关资源
                  最近更新 更多