具有const成员的类的赋值

assignment of class with const member

本文关键字:赋值 成员 const 具有      更新时间:2023-10-16

考虑以下代码:

struct s
{
    const int id;
    s(int _id):
        id(_id)
    {}
};
// ...
vector<s> v;  v.push_back(s(1));

我收到一个编译器错误,"const int id"不能使用默认赋值运算符。

Q1.为什么push_back()需要赋值运算符
A1.因为目前的c++标准是这么说的。

Q2.我该怎么办?

  • 我不想放弃const说明符
  • 我想复制数据

A2.我会使用智能指针。

Q3.我想出了一个"解决方案",看起来相当疯狂:

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

我应该避免这种情况吗?为什么(如果是的话)如果对象在堆栈上,使用placement new是否安全

C++03要求存储在容器中的元素是CopyConstructibleAssignable(请参见§23.1)。因此,实现可以根据需要决定使用复制构造和赋值。这些约束在C++11中得到了放松。明确地说,push_back操作要求是将类型CopyInsertable放入向量中(见§23.2.3序列容器)

此外,C++11容器可以在插入操作中使用move语义。

我不想放弃常量说明符

你别无选择。

s& operator =(const s& m) {
    return *new(this) s(m); 
}

未定义的行为。

几乎没有人使用const成员变量是有原因的,正因为如此。你对此无能为力。const成员变量根本不能用于你想要可赋值的类型。这些类型是不可变的,仅此而已,vector的实现需要可变性。

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

我应该避免这种情况吗?为什么(如果是的话)?如果对象在堆栈上,使用新放置是否安全?

如果可以的话,你应该避免它,不是因为它格式不正确,而是因为读者很难理解你的目标并信任这段代码。作为一名程序员,你应该致力于减少你编写的WTF/行代码的数量。

但是,这是合法的。根据

[new.delete.placement]/3

void* operator new(std::size_t size, void* ptr) noexcept;

3备注:故意不采取其他行动。

调用placement new不会分配或释放内存,相当于手动调用s的复制构造函数,根据[basic.life]/8,如果s有一个平凡的析构函数,则复制构造函数是合法的。

好的,

你应该总是用简单的步骤来思考问题。

std::vector<typename T>::push_back(args);   

需要在矢量数据中保留空间,然后将参数值分配(或复制或移动)到该位置的vector.data()[idx]的内存中。

要理解为什么不能在成员函数std::vector::push_back中使用结构,请尝试以下操作:

std::vector<const int> v; // the compiler will hate you here, 
                          // because this is considered ill formed.

格式错误的原因是类std::vector的成员函数可以调用其模板参数的赋值运算符,但在这种情况下,它是一个常量类型参数"const-int",这意味着它没有赋值运算符(给常量变量赋值毫无意义!!)。对于具有const数据成员类类型,也观察到了相同的行为。因为编译器会删除默认的赋值运算符,所以会驱逐

struct S
{
    const int _id; // automatically the default assignment operator is 
                   // delete i.e.  S& operator-(const S&) = delete;
};
// ... that's why you cannot do this
std::vector<S> v; 
v.Push_back(S(1234));

但是,如果你想保持意图并用一个格式良好的代码来表达它,你应该这样做:

class s
{
    int _id;
public:
    explicit s(const int& id) :
    _id(id)
    {};
    const int& get() const
    {
    return _id; 
    }; // no user can modify the member variable after it's initialization
};
// this is called data encapsulation, basic technique!
// ...
std::vector<S> v ; 
v.push_back(S(1234)); // in place construction

如果你想打破规则,强加一个可赋值的常量类类型,那么就按照上面的建议去做。

Q2.我该怎么办?

存储指针,最好是智能的。

vector<unique_ptr<s>> v;
v.emplace_back(new s(1));

这不是一个真正的解决方案,而是一个变通方法:

#include <vector>
struct s
{
  const int id;
  s(int _id):
    id(_id)
    {}
};
int main(){
  std::vector<s*> v;  
  v.push_back(new s(1));
  return 0;
}

这将存储s的指针,而不是对象本身。至少它编译了…;)

编辑:您可以使用智能c++11指针来增强这一点。请参阅Benjamin Lindley的回答。

在赋值运算符中使用const_cast:

S& operator=(const S& rhs)
{
    if(this==&rhs) return *this;
    int *pid=const_cast<int*>(&this->id);
    *pid=rhs.id;
    return *this;
}