std ::对象的向量和const-correctness

时间:2010-11-08 13:33:10

标签: c++ vector const-correctness

请考虑以下事项:

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;
}

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

编辑: OFC。我无法在operator =()方法中更改this-&gt; c,因此我没有看到如何使用operator =()(尽管std :: vector需要)。

10 个答案:

答案 0 :(得分:17)

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

你可以通过“复制”调用UB或什么也不做(因此不是副本)给你的类带来可怕的语义,但是为什么所有这些麻烦在UB和坏代码中跳舞呢?通过制作const你会得到什么? (提示:没什么。)你的问题是概念性的:如果一个类有一个const成员,那么这个类就是const。从根本上说,不能分配const的对象。

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

答案 1 :(得分:15)

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

我认为上面的代码片段会调用Undefined Behavior,因为试图抛弃const限定变量的常量会调用UB

答案 2 :(得分:7)

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

答案 3 :(得分:2)

存储的类型必须符合CopyConstructible 和Assignable 要求,这意味着还需要operator =。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

C ++ 03

  

$ 12.8 / 12 - “隐含声明   复制赋值运算符是隐式的   在其类的对象时定义   type被赋予其类的值   类型或类类型的值   派生自其类类型。一个程序   如果是哪一类,那就是不正确的   复制赋值运算符是隐式的   定义有:

     

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

     

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

     

- a   类类型的非静态数据成员   (或其数组)带有   无法访问的副本分配操作员,   或

     

- 一个无法访问的基类   复制赋值运算符。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

没有const_cast的解决方法。

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

答案 8 :(得分:0)

我最近碰到了相同的情况而且我使用了std :: set,因为它添加元素(insert)的机制不需要=运算符(使用&lt;运算符),这与vector&#39; s不同。机制(push_back)。

如果性能有问题,您可以尝试使用unordered_set或类似的其他内容。

答案 9 :(得分:0)

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

cppreference states(强调):

std::vector<T,Allocator>::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