【问题标题】:assignment of class with const member使用 const 成员分配类
【发布时间】:2012-07-21 01:36:38
【问题描述】:

考虑以下代码:

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。我将使用智能指针。

第三季度。我想出了一个看起来相当疯狂的“解决方案”:

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

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

【问题讨论】:

  • 关于#1,它是一个动态数组。它必须复制(或移动)元素。
  • &chris 那是我没有得到的部分,它应该复制而不是分配
  • 你可以启用C++11,代码会编译。
  • 对于Q3:你必须绝对检查自赋值,然后在new调用之前说this-&gt;~s();(因为它是UB覆盖析构函数非平凡的对象的内存)... 但我不确定您是否可以在其自己的成员函数中销毁对象。
  • 如果您希望在复制分配期间复制您的数据成员,那么声明它const 没有任何意义。为什么要const 或者为什么不想放弃const

标签: c++ vector constants assignment-operator placement-new


【解决方案1】:

C++03 要求存储在容器中的元素为 CopyConstructibleAssignable(参见 §23.1)。因此,实现可以决定使用他们认为合适的复制构造和分配。这些约束在 C++11 中得到了放松。明确地,push_back 操作要求是类型为CopyInsertable 到向量中(参见§23.2.3 序列容器)

此外,C++11 容器可以在插入操作中使用移动语义并继续操作。

【讨论】:

  • 当涉及到const 成员时,移动分配并不比复制分配好(即它也被禁用)。
  • @LucDanton 对,但不再要求类型是可分配的。
【解决方案2】:

我不想放弃 const 说明符

好吧,你别无选择。

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

未定义的行为。

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

【讨论】:

  • 为什么这是未定义的行为?
  • 相关问题:stackoverflow.com/questions/47473621/…为什么是UB?
  • 它可能不是。
  • 这是未定义的行为,正如编译器所假设的那样,const 成员是恒定的——即使在分配给整个对象期间也是如此。因此,在放置新技巧之后,编译器可能仍会使用该 const 字段的旧值,例如,如果它已被加载到寄存器中。所以UB。 std::launder() 是一种解决方法,但我不推荐它。改为使用 const 字段 private 并使用 getter 函数来读取其值。
【解决方案3】:
s& operator =(const s& m)
{
    if(this == &m) return *this;
    this->~s();
    return *new(this) s(m);
}

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

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

但是,这是合法的。根据

[new.delete.placement]/3

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

3 备注:故意不执行其他动作。

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

【讨论】:

【解决方案4】:

好的,

您应该始终以简单的步骤来思考问题。

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

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

要了解为什么你不能在成员函数 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 变量是没有意义的!!)。 具有 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

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

【讨论】:

    【解决方案5】:

    第二季度。我该怎么办?

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

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

    【讨论】:

      【解决方案6】:

      这不是真正的解决方案,而是一种解决方法:

      #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 的回答。

      【讨论】:

        【解决方案7】:

        在赋值运算符中使用 const_cast:

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

        【讨论】:

        • 我不是 100% 确定,但我很担心这可能会调用 UB
        猜你喜欢
        • 2018-05-08
        • 2020-07-04
        • 1970-01-01
        • 2017-01-12
        • 2016-05-01
        • 1970-01-01
        • 2013-03-08
        • 2017-04-06
        • 1970-01-01
        相关资源
        最近更新 更多