Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
803 views
in Technique[技术] by (71.8m points)

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

I am looking to do something like this (C#).

public final class ImmutableClass {
    public readonly int i;
    public readonly OtherImmutableClass o;
    public readonly ReadOnlyCollection<OtherImmutableClass> r;

    public ImmutableClass(int i, OtherImmutableClass o,
        ReadOnlyCollection<OtherImmutableClass> r) : i(i), o(o), r(r) {}
}

The potential solutions and their associated problems I've encountered are:

1. Using const for the class members, but this means the default copy assignment operator is deleted.

Solution 1:

struct OtherImmutableObject {
    const int i1;
    const int i2;

    OtherImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
}

Problem 1:

OtherImmutableObject o1(1,2);
OtherImmutableObject o2(2,3);
o1 = o2; // error: use of deleted function 'OtherImmutableObject& OtherImmutableObject::operator=(const OtherImmutableObject&)`

EDIT: This is important as I would like to store immutable objects in a std::vector but receive error: use of deleted function 'OtherImmutableObject& OtherImmutableObject::operator=(OtherImmutableObject&&)

2. Using get methods and returning values, but this means that large objects would have to be copied which is an inefficiency I'd like to know how to avoid. This thread suggests the get solution, but it doesn't address how to handle passing non-primitive objects without copying the original object.

Solution 2:

class OtherImmutableObject {
    int i1;
    int i2;
public:
    OtherImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
    int GetI1() { return i1; }
    int GetI2() { return i2; }
}

class ImmutableObject {
    int i1;
    OtherImmutableObject o;
    std::vector<OtherImmutableObject> v;
public:
    ImmutableObject(int i1, OtherImmutableObject o,
        std::vector<OtherImmutableObject> v) : i1(i1), o(o), v(v) {}
    int GetI1() { return i1; }
    OtherImmutableObject GetO() { return o; } // Copies a value that should be immutable and therefore able to be safely used elsewhere.
    std::vector<OtherImmutableObject> GetV() { return v; } // Copies the vector.
}

Problem 2: The unnecessary copies are inefficient.

3. Using get methods and returning const references or const pointers but this could leave hanging references or pointers. This thread talks about the dangers of references going out of scope from function returns.

Solution 3:

class OtherImmutableObject {
    int i1;
    int i2;
public:
    OtherImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
    int GetI1() { return i1; }
    int GetI2() { return i2; }
}

class ImmutableObject {
    int i1;
    OtherImmutableObject o;
    std::vector<OtherImmutableObject> v;
public:
    ImmutableObject(int i1, OtherImmutableObject o,
        std::vector<OtherImmutableObject> v) : i1(i1), o(o), v(v) {}
    int GetI1() { return i1; }
    const OtherImmutableObject& GetO() { return o; }
    const std::vector<OtherImmutableObject>& GetV() { return v; }
}

Problem 3:

ImmutableObject immutable_object(1,o,v);
// elsewhere in code...
OtherImmutableObject& other_immutable_object = immutable_object.GetO();
// Somewhere else immutable_object goes out of scope, but not other_immutable_object
// ...and then...
other_immutable_object.GetI1();
// The previous line is undefined behaviour as immutable_object.o will have been deleted with immutable_object going out of scope

Undefined behaviour can occur due to returning a reference from any of the Get methods.

question from:https://stackoverflow.com/questions/57710254/idiomatic-way-to-create-an-immutable-and-efficient-class-in-c

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
  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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...