Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"immutable" struct with non-trivial constructor

Tags:

c++

c++11

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.

like image 433
Nelfeal Avatar asked Jun 09 '16 13:06

Nelfeal


1 Answers

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';
}
like image 104
David Hammen Avatar answered Oct 27 '22 13:10

David Hammen