Search code examples
c++constantsvariable-assignmentassignment-operatoremplace

Replace a variable of a type with const members


Suppose I have a class with some constant member:

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

Now I want to store an instance of MyClass somewhere, e.g. as a global variable or as an attribute of another object.

MyClass var;

Later, I want to assign a value to var:

var = MyClass(5);

Obviously, this does not work, because the assign operator is invoked, which does not exist by default, because MyClass has a const attribute. So far so good.

My question is, how can I assign a value to var anyway? After all, var itself is not declared to be a constant.

My thoughts so far

I know that the problem does not exist if I use a pointer for var:

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

However, I would not like to use a pointer for convenience reasons.

A potential solution is to overwrite the memory with 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);

This would solve the problem, but I am not sure if this may cause memory leaks or any other issues I have not thought of due to my lack of experience in c++. Furthermore, I would have thought there must be an easier way. Is there?


Solution

  • const members are problematic in general for the very reason you discovered.

    The much simpler alternative is to make the member private and take care to provide no means to modify it from outside the class:

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

    I did not add a getter yet, because you say access via myObject.a is a hard requirement. Enabling this requires a bit of boilerplate, but it is much less hacky than modifiying something that must not be modified:

    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

    Drawback is that it does not play well with auto. If by any means you can accept myObject.a() I would suggest to use that and keep it simple.