Idiomatic Way to declare C++ Immutable Classes - c++

So I have some pretty extensive functional code where the main data type is immutable structs/classes. The way I have been declaring immutability is "practically immutable" by making member variables and any methods const.
struct RockSolid {
const float x;
const float y;
float MakeHarderConcrete() const { return x + y; }
}
Is this actually the way "we should do it" in C++? Or is there a better way?

The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:
RockSolid a(0,1);
RockSolid b(0,1);
a = b;
This would not work as the copy assignment operator would have been deleted by the compiler.
So an alternative is to rewrite your struct as a class with private data members, and only public const functions.
class RockSolid {
private:
float x;
float y;
public:
RockSolid(float _x, float _y) : x(_x), y(_y) {
}
float MakeHarderConcrete() const { return x + y; }
float getX() const { return x; }
float getY() const { return y; }
}
In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.

I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.
The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.
As an example:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
we can create one of these, but if we have a function to create it:
RockSolidLayers make_layers();
it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:
RockSolidLayers&& layers = make_layers();
or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.
Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.
And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.
In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.
One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.
Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.
A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.
For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operation, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".
Now as C++ has developed, there are more opportunities for elision. Even C++23 is going to have more advanced elision. Elision is when the data isn't logically moved or copied, but just has two different names, one inside a function and one outside.
Relying on that remains awkward.

As of c++20, workarounds with getters is no longer needed.
You can now define your own copy-assignment operator for classes that contain const member objects without undefined behavior as of c++20.
This was undefined behavior prior to c++ and remains so for complete const objects but not non-const objects with const members.
https://stackoverflow.com/a/71848927/5282154

Related

Implementing copy\move operator= for classes with const members [duplicate]

So I have some pretty extensive functional code where the main data type is immutable structs/classes. The way I have been declaring immutability is "practically immutable" by making member variables and any methods const.
struct RockSolid {
const float x;
const float y;
float MakeHarderConcrete() const { return x + y; }
}
Is this actually the way "we should do it" in C++? Or is there a better way?
The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:
RockSolid a(0,1);
RockSolid b(0,1);
a = b;
This would not work as the copy assignment operator would have been deleted by the compiler.
So an alternative is to rewrite your struct as a class with private data members, and only public const functions.
class RockSolid {
private:
float x;
float y;
public:
RockSolid(float _x, float _y) : x(_x), y(_y) {
}
float MakeHarderConcrete() const { return x + y; }
float getX() const { return x; }
float getY() const { return y; }
}
In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.
I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.
The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.
As an example:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
we can create one of these, but if we have a function to create it:
RockSolidLayers make_layers();
it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:
RockSolidLayers&& layers = make_layers();
or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.
Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.
And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.
In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.
One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.
Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.
A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.
For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operation, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".
Now as C++ has developed, there are more opportunities for elision. Even C++23 is going to have more advanced elision. Elision is when the data isn't logically moved or copied, but just has two different names, one inside a function and one outside.
Relying on that remains awkward.
As of c++20, workarounds with getters is no longer needed.
You can now define your own copy-assignment operator for classes that contain const member objects without undefined behavior as of c++20.
This was undefined behavior prior to c++ and remains so for complete const objects but not non-const objects with const members.
https://stackoverflow.com/a/71848927/5282154

Are reference members good practice? Are const members?

A coworker and I are debating whether const or reference members are ever the right thing to do. const and reference members make a class noncopyable and unmovable unless you write your own copy and move operators which ignore the reference or const members. I can't find a case where ignoring copying or moving certain members just because they are references or const makes sense.
I think having an unmovable object is very rarely logically sound, and is a choice that only has something to do with whether the class is location invariant. A noncopyable object is much more common, but the choice to make a class noncopyable has to do with whether it holds a resource which is logically noncopyable, if it represents sole ownership, etc. I can't think of a reason the sole trait of having a reference or const member or not would imply that the class should have either of these traits (noncopyable or unmovable)
Should they ever be used? Should one but not the other? What are examples?
I can't think of a reason the sole trait of having a reference or const member or not would imply that the class should have either of these traits (noncopyable or unmovable)
I think the premise is wrong.
Having a const data member or a reference data member does not make the class non-copyable: it simply makes it impossible to assign it or move from it in a destructive way.
That is because a destructive move operation is not sound for const objects or references: it would be against their semantics, since the state of a const object shall never be altered, and references cannot be unbound.
However, the fact that X is not copy-assignable or move-assignable does not compromise the possibility of constructing copies of those objects, which is - as you point out - a sound operation. The following program, for instance, will compile fine:
struct X
{
X() : x(0), rx(x) { }
const int x;
const int& rx;
};
int main()
{
X x;
X x1 = x; // Copy-initialization (copy-constructs x1 from x)
X x2 = X(); // Copy-initialization (copy-constructs x2 from a temporary)
}
The compiler will generate a copy-constructor implicitly for you, and moves will degenerate to copies. If this degeneration of move into copy is inappropriate for the semantics of your class, then you may consider not having const or reference members.

How to have const members in stl container values in C++?

I like to make my C++ member variables const if they should not be changed once the object is constructed, however, sometimes they need to be modified by STL. For example, if I have a vector of my class with const members and I try to swap two elements in the vector, STL tries to use the default generated operator=() and fails because of the const member variables.
I feel like the operator=() is like a constructor in that the whole object is being created and thus would like some way to allow operator=() while still having my const member variables.
Is there anyway to do this in C++03? If not, what about in C++11, perhaps in-place construction is for this?
class Foo {
const int _id;
static int _generate_unique_id();
public:
Foo()
: _id(_generate_unique_id()) {
}
};
vector<Foo> foo_vector;
// Fill foo_vector with several entries:
// [...]
// Try to swap the first and second elements of the vector:
swap(*foo_vector.begin(), *(foo_vector.begin() + 1));
// The above fails to compile due to const member variable _id
// prohibits us from using the default assignment operator.
A solution for storing not assignable objects in standard library containers is storing (smart) pointers to the objects. Not always ideal, but workable.
For example, if I have a vector of my class with const members and I try to swap two elements in the vector, STL tries to use the default generated operator=() and fails because of the const member variables.
Implement the "big three and a half" (default and copy constructor, assignment operator and swap), with the assignment operator explicitly skipping the reassignment if _id.
What you want is a thing like the Java immutable idiom.
This is awesome with pointers (and thus, garbage collected languages) and less awesome with value-semantic languages like C++.
You have two solutions:
1 - Make your object immutable in the interface
The member is private (or it should be), so no one but the class itself (and its friends) can modify it. So all you need is to make sure no one does inside the class (which you control) and offer no way in the protected/public interface to leave others the power to do so.
TL;DR: Make your object non const. Don't modify it inside the class. Add a const getter. Remove the setter (if any).
2 - Use a std::unique_ptr<const Data>
Now we follow the Java idiom. The object is const, but the pointer can be reattributed, which is exactly what you want.
This is actually better than the const Data * member alternative because of its exception safety.
Bonus: Don't manually call the destructor to reconstruct again the object
There's an answer proposing that.
As mentionned first by sehe, don't do that.
Your point is to increase the quality of your code, which means your code will need to be exception safe, at one point or the other. And manually playing with your object lifetime will make it unusable in quality code.
Read Herb Sutter's article on the subject: http://www.gotw.ca/gotw/023.htm
const on members doesn't just prevent the programmer from modifying the value of the member during its lifetime; it also enables compiler optimisations by specifying that attempts to modify it are undefined behaviour (see const member and assignment operator. How to avoid the undefined behavior?).
One way to do what you want is to write a nonmodifiable container that gives semantic const while leaving you as the programmer the possibility of modifying the contained value:
template<typename T> class nonmodifiable {
T t;
public:
nonmodifiable(T t): t{std::move(t)} {}
operator const T &() const { return t; }
nonmodifiable &operator=(const nonmodifiable &) = delete;
};
You can now write:
class Foo {
nonmodifiable<int> _id;
// etc.
};
and because neither _id nor its contained value are const, use the destruct-placement new dance to reassign its value:
Foo &operator=(const Foo &foo) {
if (this != &foo) {
_id.~nonmodifiable<int>();
new (&_id) nonmodifiable<int>(foo._id);
}
return this;
}

Using a class with const data members in a vector

Given a class like this:
class Foo
{
const int a;
};
Is it possible to put that class in a vector? When I try, my compiler tells me it can't use the default assignment operator. I try to write my own, but googling around tells me that it's impossible to write an assignment operator for a class with const data members. One post I found said that "if you made [the data member] const that means you don't want assignment to happen in the first place." This makes sense. I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
You have to ask whether the following constraint still holds
a = b;
/* a is now equivalent to b */
If this constraint is not true for a and b being of type Foo (you have to define the semantics of what "equivalent" means!), then you just cannot put Foo into a Standard container. For example, auto_ptr cannot be put into Standard containers because it violates that requirement.
If you can say about your type that it satisfies this constraint (for example if the const member does not in any way participate to the value of your object, but then consider making it a static data member anyway), then you can write your own assignment operator
class Foo
{
const int a;
public:
Foo &operator=(Foo const& f) {
/* don't assign to "a" */
return *this;
}
};
But think twice!. To me, it looks like that your type does not satisfy the constraint!
Use a vector of pointers std::vector<Foo *>. If you want to avoid the hassle of cleaning up after yourself, use boost::ptr_vector.
Edit: My initial stab during my coffee break, static const int a; won't work for the use case the OP has in mind, which the initial comments confirm, so I'm rewriting and expanding my answer.
Most of the time, when I want to make an element of a class constant, it's a constant whose value is constant for all time and across all instances of the class. In that case, I use a static const variable:
class Foo
{
public:
static const int a;
};
Those don't need to be copied among instances, so if it applied, that would fix your assignment problem. Unfortunately, the OP has indicated that this won't work for the case the OP has in mind.
If you want to create a read-only value that clients can't modify, you can make it a private member variable and only expose it via a const getter method, as another post on this thread indicates:
class Foo
{
public:
int get_a() const { return a; }
private:
int a;
};
The difference between this and
class Foo
{
public:
const int a;
};
is:
The const int gives you assurance that not even the implementation of the class will be able to muck with the value of a during the lifetime of the object. This means that assignment rightfully won't work, since that would be trying to modify the value of a after the object's been created. (This is why, btw, writing a custom operator=() that skips the copy of a is probably a bad idea design-wise.)
The access is different – you have to go through a getter rather than accessing the member directly.
In practice, when choosing between the two, I use read-only members. Doing so probably means you'll be able to replace the value of an object with the value of another object without violating semantics at all. Let's see how it would work in your case.
Consider your Grid object, with a width and height. When you initially create the vector, and let's say you reserve some initial space using vector::reserve(), your vector will be populated with initial default-initialized (i.e. empty) Grids. When you go to assign to a particular position in the vector, or push a Grid onto the end of the vector, you replace the value of the object at that position with a Grid that has actual stuff. But you may be OK with this! If the reason you wanted width and height to be constant is really to ensure consistency between width and height and the rest of the contents of your Grid object, and you've verified that it doesn't matter whether width and height are replaced before or after other elements of Grid are replaced, then this assignment should be safe because by the end of the assignment, the entire contents of the instance will have been replaced and you'll be back in a consistent state. (If the lack of atomicity of the default assignment was a problem, you could probably get around this by implementing your own assignment operator which used a copy constructor and a swap() operation.)
In summary, what you gain by using read-only getters is the ability to use the objects in a vector or any container with value semantics. However, it then falls to you to ensure that none of Grid's internal operations (or the operations of friends of Grid) violate this consistency, because the compiler won't be locking down the width and height for you. This goes for default construction, copy construction, and assignment as well.
I'm considering making the data member non-const, but private and only accessible by a get function, like this:
class Foo
{
private:
int a;
public:
int getA() const {return a;}
};
Is this 'as good' as const? Does it have any disadvantages?
As of c++20, using const member variables are legal without restrictions that had made it virtually unusable in containers. You still have to define a copy assignment member function because it continues to be automatically deleted when a const object exists in the class. However, changes to "basic.life" now allow changing const sub-objects and c++ provides rather convenient functions for doing this. Here's a description of why the change was made:
The following code shows how to define a copy assignment member function which is useable in any class containing const member objects and uses the new functions std::destroy_at and std::construct_at to fulfil the requirement so the new "basic.life" rules. The code demonstrates assignment of vectors as well as sorting vectors with const elements.
Compiler explorer using MSVC, GCC, CLANG https://godbolt.org/z/McfcaMWqj
#include <memory>
#include <vector>
#include <iostream>
#include <algorithm>
class Foo
{
public:
const int a;
Foo& operator=(const Foo& arg) {
if (this != &arg)
{
std::destroy_at(this);
std::construct_at(this, arg);
}
return *this;
}
};
int main()
{
std::vector<Foo> v;
v.push_back({ 2 });
v.push_back({ 1 });
v.insert(v.begin() + 1, Foo{ 0 });
std::vector<Foo> v2;
v2 = v;
std::sort(v2.begin(), v2.end(), [](auto p1, auto p2) {return p1.a < p2.a; });
for (auto& x : v2)
std::cout << x.a << '\n';
}

Swap method with const members

I want to implement a Swap() method for my class (let's call it A) to make copy-and-swap operator=(). As far as I know, swap method should be implemented by swapping all members of the class, for example:
class A
{
public:
void swap(A& rhv)
{
std::swap(x, rhv.x);
std::swap(y, rhv.y);
std::swap(z, rhv.z);
}
private:
int x,y,z;
};
But what should I do if I have a const member? I can't call std::swap for it, so I can't code A::Swap().
EDIT: Actually my class is little bit more complicated. I want to Serialize and Deserialize it. Const member is a piece of data that won't change (its ID for example) within this object. So I was thinking of writing something like:
class A
{
public:
void Serialize(FILE* file) const
{
fwrite(&read_a, 1, sizeof(read_a), file);
}
void Deserialize(FILE* file) const
{
size_t read_a;
fread(&read_a, 1, sizeof(read_a), file);
A tmp(read_a);
this->Swap(tmp);
}
private:
const size_t a;
};
and call this code:
A a;
FILE* f = fopen(...);
a.Deserialize(f);
I'm sorry for such vague wording.
I think what you really want is to have an internal data structure that you can easily exchange between objects. For example:
class A
{
private:
struct A_Data {
int x;
int y;
const int z;
A_Data(int initial_z) : z(initial_z) {}
};
std::auto_ptr<A_Data> p_data;
public:
A(int initial_z) : p_data(new A_Data(initial_z)) {}
void swap(A& rhv) {
std::swap(p_data, rhv.p_data);
}
};
This keeps the z value constant within any instance of A object internal data, but you can swap the internal data of two A objects (including the constant z value) without violating const-correctness.
After a good nights sleep I think the best answer is to use a non-const pointer to a const value -- after all these are the semantics you are trying to capture.
f0b0s, a good design principle is to design your objects to be immutable. This means that the object can't change once created. To "change" the object, you must copy the object and make sure to change the elements you want.
That being said, in this case you should look at using a copy constructor instead to copy the objects you want to swap, and then actually swap the references to the object. I can understand it'd be tempting just to be able to change the elements of an object under the hood, but it'd be better to make a copy of the object and replace the references to that object with the NEW object instead. This gets you around any const nastiness.
Hope this helps.
I suggest you use pointers to the instances. The pointers can be swapped much easier than the data in the class or struct.
The only way to swap a constant value is to create another object, or clone the current object.
Given a struct:
struct My_Struct
{
const unsigned int ID;
std::string name;
My_Struct(unsigned int new_id)
: ID(new_id)
{ ; }
};
My understanding is that you want to swap instances of something like My_Struct above. You can copy the mutable (non-const) members but not the const member. The only method to alter the const member is to create a new instance with a new value for the const member.
Perhaps you need to rethink your design.
IMHO you must consider not to swap CONST members.
PD: I think you could consider to use reflection in your approach. so you don't have to maintain the function.
This is why const_cast was created. Just remember not to shoot your foot off.
Edit: OK, I concede - const_cast wasn't made for this problem at all. This might work with your compiler, but you can't count on it and if demons come flying out of your nostrils, please don't blame me.
tl;dr; : It's Undefined Behavior.
Reference/reason: CppCon 2017: Scott Schurr “Type Punning in C++17: Avoiding Pun-defined Behavior, #24m52s +- ”
My interpretation, by example:
Suppose you create an object of type T, which have some const members. You can pass this object as a non-const reference to a function f(&T) that manipulates it, but you'd expect the const members to remain unalterable after the call. swap can be called in non-const references, and it can happen inside the function f, breaking the premise of const members to the caller.
Every part of your code that uses swap would have to assert that the object of type T being swapped does not belong to any context where the const members are assumed constant. That is impossible to automatically verify*.
*I just assumed that this is impossible to verify because it seems like an extension of the undecidability of the halting problem.