【问题标题】:std::vector of objects and const-correctness对象的 std::vector 和 const 正确性
【发布时间】:2011-05-06 16:09:50
【问题描述】:

考虑以下几点:

class A {
public:
    const int c; // must not be modified!

    A(int _c)
    :   c(_c)
    {
        // Nothing here
    }

    A(const A& copy)
    : c(copy.c)
    {
        // Nothing here
    }    
};



int main(int argc, char *argv[])
{
    A foo(1337);

    vector<A> vec;
    vec.push_back(foo); // <-- compile error!
    
    return 0;
}

显然,复制构造函数是不够的。我错过了什么?

编辑:
办公室。我无法在operator=() 方法中更改this-&gt;c,所以我看不到如何使用operator=()(尽管std::vector 需要)。

【问题讨论】:

  • 什么是编译错误? 不要含糊其辞,成为ace;写一个propertest-case!
  • 我认为你有一个选择:要么失去 const,要么失去使用向量的能力。如果您开始解决它并允许operator= 修改 const 成员,那么您现在已经为任何一段代码提供了执行相同操作的方法。
  • const int c; // 不得修改!您在上面的评论,这是否意味着“c”不应该被使用 A 类的对象或 A 类本身的成员修改?
  • @anand: c 只能在构造函数中设置。在我的具体情况下,我有一个指向某个父节点的指针。任务 * 常量父级;此指针不得重新定位,因此不允许在课堂内或外部对 c 进行更改。
  • @eisbaw:对我来说,选择 'const int c' 作为 A 类的成员是问题的根本原因。因此我的问题。有一个 operator=() 并使用 const_cast 来抛弃 const 来为“c”赋值,这听起来像是修复编译器错误的技巧,而不是解决实际问题的方法。

标签: c++ vector const-correctness


【解决方案1】:

我不确定为什么没有人这么说,但正确的答案是删除const,或将A* 存储在向量中(使用适当的智能指针)。

您可以通过让“复制”调用 UB 或什么都不做(因此不是复制品)给您的班级提供可怕的语义,但是为什么所有这些麻烦都围绕着 UB 和糟糕的代码跳舞呢?制作const 会得到什么? (提示:什么都没有。)你的问题是概念性的:如果一个类有一个 const 成员,那么这个类就是 const。 const 对象从根本上说是无法赋值的。

只需将其设为非常量私有,并以不可变的方式公开其值。对于用户来说,这是等价的,const-wise。它允许隐式生成的函数正常工作。

【讨论】:

  • 说得好。我想是const int c 之后的评论让我认为@eisbaw 的情况没有其他解决方法。古老的格言是正确的:“为什么要为自己复杂化?”
  • 其实“只去掉'const'”并不容易。为什么?因为在成员字段中删除'const'有效地强制你在构造函数参数中删除'const',这反过来又迫使你在使用它的地方删除'const'等等......(我认为,这个想法应该是清除)。带有指针的 std::vector 的解决方案应该可以工作(尽管它不是很优雅)。顺便说一句,在 VS2010 中,您可以将不带赋值运算符的元素 push_back 到 std::vector 中。
  • @DmitriiSemikin 从字段中删除 const 不会影响参数。如果您按值分配,则该值复制到非常量字段中就好了。 C类{int mX; ClassName(const int x) : mX(x) {} };
  • @GManNickG 还有另一种观点。复制一个对象后变成另一个对象。因此,成员的常量性不适用于对象复制上下文。不幸的是,现在我必须在 const 成员语义和分配给对象之间做出选择。
【解决方案2】:

STL 容器元素必须是可复制构造和可分配的1(您的类A 不是)。你需要重载operator =

1 :§23.1The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignabletypes


编辑

免责声明:我不确定以下代码是否 100% 安全。如果它调用 UB 或其他什么,请告诉我。

A& operator=(const A& assign)
{
    *const_cast<int*> (&c)= assign.c;
    return *this;
}

编辑 2

我认为上面的代码 sn-p 调用未定义行为,因为试图抛弃 const 限定变量的 const-ness 调用 UB

【讨论】:

  • @eisbaw:抱歉,您不能在重载的operator= 中分配给this-&gt;c[因为它是一个常量]。
  • @eisbaw:你不能写一个空的 operator= 来简单地忽略你传递给它的任何东西吗?这维护了所需的接口,但保留了 c 的完整性。
  • @Prasoon:如果 *this 被声明为 const(例如 A const obj (3); obj = A(42);),那么它就是 UB。否则它只是一个bad idea
  • @Roger:是的,没错。 ` §7.1.​5.1/4` 说 Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior. 但我们这里没有任何 const 对象,所以虽然这是个坏主意,但应该没问题。
  • 其实你的说法并不完全正确。 vector 要求元素是可复制构造和可分配的,但并非所有容器都强制执行此操作(列表不强制分配)。
【解决方案3】:

您缺少一个赋值运算符(或复制赋值运算符),the big three 之一。

【讨论】:

    【解决方案4】:

    存储的类型必须满足CopyConstructible和Assignable的要求,也就是说也需要operator=。

    【讨论】:

      【解决方案5】:

      可能是assignment operator。编译器通常会为您生成一个默认值,但该功能被禁用,因为您的类具有非平凡的复制语义。

      【讨论】:

        【解决方案6】:

        我认为您正在使用的向量函数的 STL 实现需要赋值运算符(请参阅标准中的 Prasoon 引用)。但是,根据下面的引用,由于代码中的赋值运算符是隐式定义的(因为它没有显式定义),因此您的程序格式错误,因为您的类也有一个 const 非静态数据成员。

        C++03

        $12.8/12 - “隐式声明 复制赋值运算符是隐式的 当其类的对象定义 类型被赋予其类的值 类型或类类型的值 派生自其类类型。一个程序 如果类是不合格式的 复制赋值运算符是隐式的 定义有:

        — const 类型的非静态数据成员,或

        ——非静态数据 引用类型的成员,或

        ——一个 类类型的非静态数据成员 (或其数组)与 无法访问的复制赋值运算符, 或

        — 具有不可访问的基类 复制赋值运算符。

        【讨论】:

          【解决方案7】:

          没有const_cast 的解决方法。

          A& operator=(const A& right) 
          { 
              if (this == &right) return *this; 
              this->~A();
              new (this) A(right);
              return *this; 
          } 
          

          【讨论】:

          • 那不就是把一种未定义行为换成另一种吗?
          • A const obj (42); obj = obj;
          • @Roger Pate。正确的。需要检查。我修好了。
          • @TheUndeadFish。 UB 在哪里?
          • @Alexey:UB 发生在派生类上;这只是实现复制分配的一种糟糕方式。
          【解决方案8】:

          我最近遇到了同样的情况,我使用了 std::set 代替,因为它添加元素的机制(插入)不需要 = 运算符(使用

          如果性能有问题,您可以尝试 unordered_set 或其他类似的东西。

          【讨论】:

            【解决方案9】:

            您还需要实现一个复制构造函数,如下所示:

            class A {
            public:
                const int c; // must not be modified!
            
                A(int _c)
                ...
            
                A(const A& copy)
                ...  
            
                A& operator=(const A& rhs)
                {
                    int * p_writable_c = const_cast<int *>(&c);
                    *p_writable_c = rhs.c;
                    return *this;
                }
            
            };
            

            特殊的const_cast 模板采用指针类型并将其转换回可写形式,用于此类场合。

            应该注意const_cast 的使用并不总是安全的,请参阅here

            【讨论】:

              【解决方案10】:

              我只想指出,从 C++11 及更高版本开始,问题中的原始代码编译得很好!完全没有错误。但是,vec.emplace_back() 会是一个更好的调用,因为它在内部使用“placement new”,因此效率更高,将对象复制到向量末尾的内存中,而不是额外的中间副本。

              cppreference states(强调):

              std::vector&lt;T,Allocator&gt;::emplace_back

              将一个新元素附加到容器的末尾。元素是通过std::allocator_traits::construct构造的,一般使用placement-new在容器提供的位置就地构造元素。

              这是一个快速演示,显示 vec.push_back()vec.emplace_back() 现在都可以正常工作。

              在这里运行它:https://onlinegdb.com/BkFkja6ED

              #include <cstdio>
              #include <vector>
              
              class A {
              public:
                  const int c; // must not be modified!
              
                  A(int _c)
                  :   c(_c)
                  {
                      // Nothing here
                  }
              
                  // Copy constructor 
                  A(const A& copy)
                  : c(copy.c)
                  {
                      // Nothing here
                  }    
              };
              
              int main(int argc, char *argv[])
              {
                  A foo(1337);
                  A foo2(999);
              
                  std::vector<A> vec;
                  vec.push_back(foo); // works!
                  vec.emplace_back(foo2); // also works!
                  
                  for (size_t i = 0; i < vec.size(); i++)
                  {
                      printf("vec[%lu].c = %i\n", i, vec[i].c);
                  }
                  
                  return 0;
              }
              

              输出:

              vec[0].c = 1337
              vec[1].c = 999
              

              【讨论】:

                猜你喜欢
                • 2021-02-26
                • 2020-03-29
                • 1970-01-01
                • 2011-03-04
                • 2021-06-02
                • 2021-11-10
                • 2022-01-10
                • 2022-08-04
                • 1970-01-01
                相关资源
                最近更新 更多