Idiomatic way to create an immutable and efficient class in C++

  1. You truly want immutable objects of some type plus value semantics (as you care about runtime performance and want to avoid the heap). Just define a struct with all data members public.

    struct Immutable {
        const std::string str;
        const int i;
    };
    

    You can instantiate and copy them, read data members, but that’s about it. Move-constructing an instance from an rvalue reference of another one still copies.

    Immutable obj1{"...", 42};
    Immutable obj2 = obj1;
    Immutable obj3 = std::move(obj1); // Copies, too
    
    obj3 = obj2; // Error, cannot assign
    

    This way, you really make sure every usage of your class respects the immutability (assuming no one does bad const_cast things). Additional functionality can be provided through free functions, there is no point in adding member functions to a read-only aggregation of data members.

  2. You want 1., still with value semantics, but slightly relaxed (such that the objects aren’t really immutable anymore) and you’re also concerned that you need move-construction for the sake of runtime performance. There is no way around private data members and getter member functions:

    class Immutable {
       public:
          Immutable(std::string str, int i) : str{std::move(str)}, i{i} {}
    
          const std::string& getStr() const { return str; }
          int getI() const { return i; }
    
       private:
          std::string str;
          int i;
    };
    

    Usage is the same, but the move construction really does move.

    Immutable obj1{"...", 42};
    Immutable obj2 = obj1;
    Immutable obj3 = std::move(obj1); // Ok, does move-construct members
    

    Whether you want assignment to be allowed or not is under your control now. Just = delete the assignment operators if you don’t want it, otherwise go with the compiler-generated one or implement your own.

    obj3 = obj2; // Ok if not manually disabled
    
  3. You don’t care about value semantics and/or atomic reference count increments are ok in your scenario. Use the solution depicted in @NathanOliver’s answer.

Leave a Comment