6

Let's say I have to read some configuration files to set fields of a structure. As soon as the file has been read and the structure initialized, its field's values should not be changed.

I read many times it was bad to have structures with const fields. But then, how to make sure its fields are not changed ?

Let's come back to the case described above. I have a function supposed to read a file given as argument and instantiate the structure with the values coming from the file. Ideally, this function would return this structure by value. But if I do so with non constant fields, the client can change the values.

A solution would be to have a class to wrap the file reader, with a non const member to the read structure, and a const accessor to read the structure and its fields. But it not very satisfying.

Is there an idiom for this situation?

9
  • 6
    Encapsulation: You can make the members private and only provide non-mutating getters. Commented Mar 21 at 14:41
  • 1
    the client can change the values - and this is fine. Your function returned values read from file, the user may choose to accept that or override some of them. I can guarantee that at some point someone will need to say "I know the file can contain X, but X doesn't make sense anymore, I need Y". Commented Mar 21 at 14:55
  • @NathanOliver Yes, I considered that, but then, it is not anymore a simple data aggregation. It is... a class. :) Commented Mar 21 at 15:06
  • @Yksisarvinen No, I want to be sure tha used values are the ones coming from the file. Commented Mar 21 at 15:07
  • 1
    Although I agree that it's usually bad practice to have a struct/class with const members, there are never any absolute rules and sometimes doing something that is usually bad practice can be the right thing in a certain situation. So perhaps just create your struct with const values and if it works perfectly for what you need, then don't worry about it. Commented Mar 21 at 17:19

2 Answers 2

7

As @NathanOliver commented, you can make all the parameters private members, and expose public getters (and no setters).

Something like:

#include <string>

class Config {
public:
    int   GetP1() const { return m_p1; }
    float GetP2() const { return m_p2; }

    bool LoadFromFile(std::string const& filename) {
        // populate m_p1, m_p2 etc.
        return true;
    }

private:
    int   m_p1{};
    float m_p2{};
};

Note that there is also a public method to initialize the configuration from the file.

Sign up to request clarification or add additional context in comments.

2 Comments

As said to @NathanOliver, it is then a class. :)
@Oodini • Such a class is logically const (and as such, more closely follows the principles of value semantics), which avoids the problems of having const member variables. I use this pattern in C++ a lot. It's an excellent pattern. I wish the language had better core support for it, because it has a lot of annoying boilerplate. C++: all the defaults are wrong.
5

I like to return a std::shared_ptr<T const>. With this, I can even cache the results of reading a specific (immutable) configuration value within my config system (returning the same pointer if it already exists, or listening for the configuration to change and when it does discarding my cache, or whatever).

The configuration structure T is mutable, which is great when I'm creating it. I just make it immutable when I'm sharing it. The user is free to copy it and modify it if they want.

The shared pointer ness means the return value is nullable; if loading the configuration fails, we return a null shared pointer. The shared ness means that users don't have to carefully husband their calls for this data: I can cache it internally.

While I am usually dead-set against shared pointer usage, using it for immutable shared data is the one big exception.

struct config {
  int width = 0;
  int height = 0;
  rgb color = {};
  static std::shared_ptr<config const> Load( std::string_view src );
};

3 Comments

Interesting idea !
This is generally a good approach. I'd rather return a unique_ptr<config> from the method that loads though. It keeps intended application logic ("only read and then never modify") outside of this class. It seamlessly converts to a shared_ptr<config const>, at which point you can even share the object across threads etc. One guarantees exclusive mutating access, the other shared read-only access.
@UlrichEckhardt The point is that the loader is returning a possibly shared resource; like, if you say "LoadUser(username)" and the program knows the user is unchanged, it returns literally the same config (instead of creating and parsing a new one). If you return a unique ptr to the config, you cannot provide this feature within LoadUser. Some low level functions may actually work on a unique ptr or even a raw config object (I'd work on a raw config object, even an optional config return, instead of a unique ptr when loading it)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.