【问题标题】:Replace a variable of a type with const members用 const 成员替换类型的变量
【发布时间】:2021-09-02 04:28:21
【问题描述】:

假设我有一个包含一些常量成员的类:

class MyClass {
public:
   MyClass(int a) : a(a) {
   }
   MyClass() : MyClass(0) {
   }
   ~MyClass() {
   }
   const int a;
};

现在我想在某处存储MyClass 的实例,例如作为全局变量或作为另一个对象的属性。

MyClass var;

稍后,我想给var赋值:

var = MyClass(5);

显然这样不行,因为调用了assign操作符,默认不存在,因为MyClass有一个const属性。到目前为止一切顺利。

我的问题是,我怎样才能给var 赋值呢? 毕竟var 本身并没有被声明为常量。

到目前为止我的想法

如果我使用var的指针,我知道问题不存在:

MyClass *var;
var = new MyClass(5);

但是,出于方便的原因,我不想使用指针。

一种可能的解决方案是用placement new 覆盖内存:

template<class T, class... Args>
T &emplaceVar(T &myVar, Args&&... args) {
   myVar.~T(); // free internal memory
   return *new (&myVar) T(args...);
}

emplaceVar(var, 5);

这将解决问题,但我不确定这是否会导致内存泄漏或由于我缺乏 c++ 经验而没有想到的任何其他问题。此外,我原以为必须有一种更简单的方法。有吗?

【问题讨论】:

  • "毕竟,var 本身并没有被声明为常量。"但是您在任何对象中标明其成员为const,无论该实例是否为const
  • 用placement-new覆盖const子对象会导致未定义的行为,基本上
  • 你可以为var写一个重载的赋值运算符——当然这将无法修改a
  • @Samufi 因为当某些东西是const 时,编译器假定该值不会改变。我建议阅读std::launder。我自己并不完全理解它,但它的许多例子都是关于将新的东西放入const
  • 你想做var = MyClass(5);,它修改了a,因此你希望a是可变的。你可以有一个私人的int actual_a; 和一个公共的int const&amp; a;,它指的是actual_a

标签: c++ constants variable-assignment assignment-operator emplace


【解决方案1】:

const 成员通常会因为您发现的原因而存在问题。

更简单的替代方法是创建成员 private 并注意不提供从类外部修改它的方法:

class MyClass {
public:
   MyClass(int a) : a(a) {
   }
   MyClass() : MyClass(0) {
   }
   ~MyClass() {
   }
private:
   int a;
};

我还没有添加吸气剂,因为您说通过myObject.a 访问是一项硬性要求。启用此功能需要一些样板文件,但它比修改不得修改的内容要简单得多:

class MyClass {
public:
   struct property {
       const int& value;
       operator int(){ return value;}
       property(const property&) = delete;
   };

   MyClass(int a = 0) : value(a) {}
private:
   int value;
public:
    property a{value};
};

int main(){
    MyClass myObject{5};
    int x = myObject.a;
    //myObject.a = 42; // error
    //auto y = myObject.a; // unexpected type :/
}

Live Demo

缺点是它不能很好地与auto 配合使用。如果您可以通过任何方式接受myObject.a(),我建议您使用它并保持简单。

【讨论】:

  • auto的处理还不错,y仍然是只读的int
  • @Caleth 实际上auto 不是问题,而是在推断类型时错误的期望,想象一个foo&lt; decltype(x)&gt;。还有一个我忘了提的问题是,很容易得到一个悬空引用
  • 你至少应该删除复制构造函数。
  • 可以有一个名为(比如)actual_aprivate 成员,并将a 指定为constactual_a 的引用。 MyClass 的所有构造函数都需要正确初始化 a,因此它引用 actual_a 成员(在其初始化列表中),但程序员定义的赋值运算符可以重新分配 actual_a 而无需(或被允许)更改内容a 指的是。
  • @Peter 但这仍然会阻止分配,不是吗? ...哦,好吧,我想我明白了。也许我会在找到时间时更新答案
【解决方案2】:

我怎样才能给 var 赋值?

您可以使用用户定义的赋值运算符来做到这一点:

class MyClass {
public:
   MyClass &operator=(const MyClass &o)
   {
         // Implement your assignment here

         return *this;
   }

   // ...
};

你的赋值运算符可以做任何operator= 重载可以做的事情。它唯一不能做的就是将任何东西分配给它的const 类成员。那是因为它是恒定的。

如果一个类没有用户定义的赋值运算符,默认的赋值运算符会从assigned-from对象的同一个成员中分配assigned-to对象的每个成员。但是,默认赋值运算符将从任何具有 const 成员的类中删除,因为这当然不再可能。

在您的用户定义运算符中,您可以做任何事情来从另一个对象中分配这些对象之一。它唯一不能做的是任何其他类方法都不能做的事情:修改const 类成员。

您提到了手动调用析构函数和放置 new。这是可能的,前提是满足所有必要的要求并小心避免未定义的行为。但是,从技术上讲,这不是赋值,而是手动销毁和构造另一个对象。

【讨论】:

    猜你喜欢
    • 2020-09-29
    • 2012-01-26
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 2012-05-25
    • 1970-01-01
    • 2014-12-08
    相关资源
    最近更新 更多