I have been looking for a way to declare some sort of immutable type with a non-trivial constructor. My current goal is to read data from a file to construct an object so that it cannot be modified subsequently. It resembles a POD type, except I need data from a file, so the constructor has to read it.
Through my research and experiments, I have thought of three ways to do just that. Basically, my question is : is there any better way to do what I want ?
In the following exemple codes, I will use std::cin
as a substitute for the file. First off, here is the obvious class-with-getters way :
class A {
public:
A() { std::cin >> m_i; }
int i() { return m_i; }
private:
int m_i;
};
As a matter of fact, I am having trouble with this solution, simply because of the getter(s). After all, it is kind of a POD type, and I would like it to be treated as such, with public data members. Also, I just don't like getters. So I tried it with some const
-ness and by tweaking the constructor :
struct B {
B() : B(B::fromFile()) {
}
B(int i) : i(i) {
}
const int i;
private:
static B fromFile() {
int i;
std::cin >> i;
return B(i);
}
};
There are several problems here. I need to delegate to a static method, because I cannot get the value of the members directly in the constructor's initializer list. This method needs to create a copy of every member (here it's just i
) and initialize them separately, so that it can pass them to another constructor before using the copy constructor to finally construct the initial object. Also, it takes a lot more lines of code because of the new constructor and the static method.
So, this approach seems doomed. Then I realized, what I really want is every instance of that class/struct to be const
. But, as far as I know, there is no way to force a user to use the const
keyword every time. So, I thought about using
alias declarations. A bit like what the standard library does for const_reference
and such (in pretty much every container). Only in this case, it would be the other way around : the type would be called NonConstType
, or let's say MutableType
, and the alias would be declared like so :
using Type = const MutableType;
And since I don't want to pollute the namespace, let's use a Mutable
namespace. Here is what the code looks like :
namespace Mutable {
struct C {
C() { std::cin >> i; }
int i;
};
}
using C = const Mutable::C;
This way, I can provide an "immutable" class that handles like a C struct (without getters) but can still be constructed with data coming from different files. Also, the mutable version is still available, which I think might be a good thing after all.
So, is there another way ? Are there benefits or drawbacks I didn't think about, in any of these three codes ?
A full testing code can be found here.
You're using C++11, so why don't you use aggregate initialization?
#include <iostream>
struct Foo {
const int val; // Intentionally uninitialized.
};
struct Foo create_foo_from_stream (std::istream& stream) {
int val;
stream >> val;
return Foo{val};
}
int main () {
Foo foo (create_foo_from_stream(std::cin));
std::cout << foo.val << '\n';
}
The only way to initialize struct Foo
is via aggregate initialization or copy construction. The default constructor is implicitly deleted.
Note that in C++14, you can use a default member initializer and still use aggregate initialization:
#include <iostream>
struct Foo {
const int val = 0; // Prevents aggregate in C++11, but not in C++14.
};
struct Foo create_foo_from_stream (std::istream& stream) {
int val;
stream >> val;
return Foo{val};
}
int main () {
Foo foo (create_foo_from_stream(std::cin));
Foo bar; // Valid in C++14.
std::cout << foo.val << bar.val << '\n';
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With