48 ms·
> In languages with constructors, you can enforce invariants in your class and ensure that no object that does not respect the invariants exists in your system
by js8 9y ago
> In languages with constructors, you can enforce invariants in your class and ensure that no object that does not respect the invariants exists in your system
Actually, it's not enough. If you can mutate the class variables (attributes), then you can always create a class that doesn't have these invariants enforced. The only way that could completely prevent invariants in the class being disrespected would be to have a strong, possibly dependent, type system.
For example, consider a function (method) that takes two objects of the same class and creates another object of the same class based on the two. During the construction of the returned object, invariants can be broken, and if this function contains an error, it will return an invalid object.
This is especially problematic with the RAII pattern. Because resource acquisition can fail, you have to allow for "broken" objects to be returned, which represent the resource not being acquired. In functional programming, this is done with sum types (and in general way with Maybe, for instance).
I would say I am against RAII pattern, because it gives people a false sense of security, as you have to deal with the resource not being acquired anyway (either by returning object in incorrect state or by exception). But if you forfeit RAII, you might as well get rid of user constructors and use factory methods (and simple data constructors and sum types) everywhere for simplicity and consistency.
- dragonwriter 9y ago> Actually, it's not enough. If you can mutate the class variables (attributes), then you can always create a class that doesn't have these invariants enforced. The only way that could completely prevent invariants in the class being disrespected would be to have a strong, possibly dependent, type system. You can completely avoid it if the invariants are expressed as validation methods, and mutation of state isn't direct but through a mechanism initially updates shadow state and guarantees that validators are run and failed validations result in both the original state being preserved and an error being signalled, and only successful validation results in state updates. A sufficiently robust type system is superior than this kind of runtime check in all kinds of ways, but is not the only way to avoid invalid state with mutable objects.
- js8 9y agoRight. What you are talking about is essentially a dynamic dependent type system.
- jcelerier 9y ago> This is especially problematic with the RAII pattern. Because resource acquisition can fail, you have to allow for "broken" objects to be returned, which represent the resource not being acquired. In functional programming, this is done with sum types (and in general way with Maybe, for instance). ... but with RAII you can't have invalid objects (if you actually care about your invariants of course). If you do :: class my_positive_int { int m_x{}; public: my_class(int x) { if(x < 0) throw invariant_broken; m_x = x; } int get() const { return m_x; } void set(my_positive_int other) { m_x = other.m_x; } }; then at no point in your program, cue undefined behaviour such as reinterpreting the bytes of your class as something else, can you have a my_positive_int with x < 0.