开发者

Do you consider multiple initialization steps "poor form"?

开发者 https://www.devze.com 2023-02-03 09:51 出处:网络
I\'m writing a physics simulation (Ising model) in C++ that operates on square lattices. The heart of my program is my Ising class with a constructor that calls for the row and column dimensions of th

I'm writing a physics simulation (Ising model) in C++ that operates on square lattices. The heart of my program is my Ising class with a constructor that calls for the row and column dimensions of the lattice. I have two other methods to set other parameters of the system (temperature & initial state) that must get called before evolving the system! So, f开发者_如何学编程or instance, a sample program might look like this

int main() {
    Ising system(30, 30);
    system.set_state(up);
    system.set_temperature(2);

    for(int t = 0; t < 1000; t++) {
        system.step();
    }

    return 0;
}

If the system.set_*() methods aren't called prior to system.step(), system.step() throws an exception alerting the user to the problem. I implemented it this way to simplify my constructor; is this bad practice?


It is recommended to put all mandatory parameters in the constructor whenever possible (there are exceptions of course, but these should be rare - I have seen one real-world example so far). This way you make your class both easier and safer to use.

Note also that by simplifying your constructor you make the client code more complicated instead, which IMO is a bad tradeoff. The constructor is written only once, but caller code may potentially need to be written many times more (increasing both the sheer amount of code to be written and the chance of errors).


Not at all, IMO. I face the same thing when loading data from external files. When the objects are created (ie, their respective ctors are called), the data is still unavailable and can only be retrieved at a later stage. So I split the initialisation in different stages:

  1. constructor
  2. initialisation (called by the framework engine when an object is activated for the first time)
  3. activation (called each time an object is activated).

This is very specific to the framework I'm developing, but there is no way to deal with everything using just the constructor.

However, if you know the variables at the moment the ctor is called, it's better not to complicate the code. It's a possible source of headaches for anyone using your code.


IMO this is poor form if all of these initialization steps must be invoked every time. One of the goals of well-designed software is to minimize the opportunities to screw up, and having multiple methods which must be invoked before an object is "usable" simply makes it harder to get right. If these calls were optional then having them as separate methods would be fine.

Share and enjoy.


The entire point in a class is to present some kind of abstraction. As a user of a class, I should be able to assume that it behaves like the abstraction it models.

And part of that is that the class must always be valid. Once the object has been created (by calling the constructor), the class must be in a meaningful, valid state. It should be ready to use. If it isn't, then it is no longer a good abstraction.


If the initialization methods must be called in a specific order then I would wrap the call to them in their own method as this indicates that the methods are not atomic on their own so the 'knowledge' of how they should be called should be held in one place.

Well that's my opinion, anyway!


I'd say that setting the initial conditions should be separate from the constructor if you plan to initialize and run more than one transient on the same lattice.

If you run a transient and stop, then it's possible to move setting the initial conditions inside the constructor, but it means that you have to pass in the parameter values in order to do this.

I fully agree with the idea that an object should be 100% ready to be used after its constructor is called, but I think that's separate from the physics of setting the initial temperature field. The object could be fully usable, yet have every node in the problem at the same temperature of absolute zero. A uniform temperature field in an insulated body isn't of much interest from a heat transfer point of view.


As another commentator pointed out, having to call a bunch of initialisation functions is poor form. I would wrap this up in a class:

class SimulationInfo
{
private:
    int x;
    int y;
    int state;
    int temperature;

public:
    SimulationArgs() : x(30), y (30), state(up), temperature(2) { }; // default ctor
    // custom constructors here!

    // properties
    int x() const { return x; };
    int y() const { return y; };
    int state() const { return state; };
    int temperature() const { return temperature; };
};  // eo class SimulationInfo


class Simulation
{
private:
    Ising m_system;

public:
    Simulation(const SimulationInfo& _info) : m_system(_info.x(), _info.y())
    {
        m_system.set_state(_info.state());
        m_system.set_temperature(_info.temperature());
    } // eo ctor


    void simulate(int _steps)
    {
        for(int step(0); step < _steps; ++steps)
            m_system.step();
    } // eo simulate
}; // eo class Simulation

There are otherways, but this makes things infinitely more usable from a default setup:

SimulationInfo si; // accept all defaults
Simulation sim(si);
sim.simulate(1000);
0

精彩评论

暂无评论...
验证码 换一张
取 消