Question

So I'm writing a network simulator in C++ as part of a university project. Right now, I have a class structure that looks something like:

//includes type aliases
#include "GlobalTypes.h"

//main body of simulator, contains various static members and methods that define behavior of simulator
class mySimulator : public myErrorHandler {static member1...static method1()...};

//these are things like a file IO handler, simulator environment initialization, etc
class largeSimulatorSubcomponent1 : private Simulator {static member1...static method1()...};
class largeSimulatorSubcomponent2 : private Simulator {static member1...static method1()...};
class largeSimulatorSubcomponent3 : private Simulator {static member1...static method1()...};

//various non-static object classes that are manipulated 
//by the simulator and each other to perform the simulation
class cellTower{member1...method1()...};
class cellPhone{member1...method1()...}; 
class dataContainer1{member1...method1()...};
class dataContainer2{member1...method1()...};
class etc{member1...method1()...};

In a general sense, the mySimulator and largeSimulatorSubComponent classes form the "universe" and control the external behavior of the various objects that are created in order to perform the simulation.

For my point of view, my structure makes sense because the "Simulator" is not ever created more than once, so it doesn't seem necessary to allow it to be a non static class that can be traditionally instantiated. However, I've come to understand that static classes are typically frowned upon with many suggesting that using namespaces is better. At first, this is what I was doing, but I made the switch to classes with static members and methods because that allowed me control over what parts of the simulator were allowed to access the other parts.

I fully expect that this project will be taken over by students in the future, and I am trying to make the structure as clear and safe for them to use as possible, and I know that a lot of them will not be experts in C++ and may inadvertently cause bugs or implement behavior in a logically inconsistent ways. I am trying to protect them from that by carefully restricting access between parts of the program with clear logical encapsulation, const correctness, error handling, etc. I believe that my current structure allows that, but I'm interested to hear any suggestions or comments on my logic/structure.

Also, on a point of lesser importance, what is your opinion on using global aliases in this manner?

using typename1 = float;  //typename 1 & 2 serve to "label" floats 
using typename2 = float;  //that are used to represent a certain category of
                          //values that important to the simulator;
Était-ce utile?

La solution

Using namespaces is the idiomatic approach, and should be preferred over classes with static members.

  • If you want to keep some parts of the namespace private, the idiomatic approach is to give those parts internal linkage and put them into a separate compilation unit. Internal linkage can be achieved by declaring the function or object static (not for class members!) or by putting it into an anonymous namespace.
  • If you need private details in a header file, the idiomatic approach is to declare a namespace detail {...} (you can't call it private because it's a reserved word).

Alternatively, consider writing normal classes, but only creating one static instance – this likely has testability advantages as well. Even better than a static instance, construct it in your main() since that gives you more control over initialization and error handling.

While classes with static members are necessarily common in Java (which has no choice in the matter), such designs do not make good use of the C++ language. This runs against some features of the language (linkage, classes/structs are for grouping data or for representing things with a lifetime). In larger projects, there are also issues around compilation times and binary stability.

There is one exception: structs with only static constexpr or typedef members are extremely useful for template metaprogramming, but you'll likely want to avoid that :)

Autres conseils

Edit - I forgot to directly answer the question:

Should I use a class with only static members to encapsulate my program?

No, because it doesn't encapsulate anything. Detailed discussion follows below.

Amon's answer mentions two reasons why you might choose this structure: roughly, that you want to write bad code in a different language, or that you're providing trait or policy mechanisms for templated code. Neither apply here AFAICT.


Now to address your comment that

I don't have any global variables in my program

The features of global variables are:

  1. top-level (global) namespace scope
  2. static storage duration:

    All objects declared at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern

So, your static class members differ from globals only on the first point, which is IMO often the less important. Namespace-scope variables aren't any better in this respect.

Now, we should consider the commonly-held disadvantages of globals. I'm going to refer to the second-highest answer (at the time of writing) to Why is Global State so Evil?, because it breaks the concerns out into a convenient list.

  • Bugs from mutable global state

    Yes, this applies to both your static-class and namespace-global styles. At some level of complexity, the state of the simulation will become hard to predict as there are too many entities freely mutating shared global state.

    Specifically for the non-expert students you mention, this means that apparently-innocuous changes may cause cascades of apparently-unrelated errors. This stuff would be hard for an expert with complete knowledge of the system to debug (which is why an expert wouldn't design it this way), but it'll be completely overwhelming for students.

  • Poor testability

    Yes, this applies - testing entities individually is at best unnecessarily hard. At worst, mocking global state for test purposes may have to be done partly by your build system instead of being manageable in code.

    This means you're setting a bad example for students by forcing them to write code which cannot easily be unit-tested. It's always tempting to skip unit tests even in a framework where they are easy to set up.

  • Inflexibility

    Yes, this applies - what if two entities should have differing views of reality at a single point in time? What if you later want to run multiple different simulations in the same process?

  • Function impurity

    Yes, this applies: your whole design revolves around implicit global side-effects. This is the flip-side of Bugs from mutable global state - that predicts whole-program bugs, and this predicts bugs in how individual components are built up from functions in the first place. Both problems are related, because they're both coupled to the same mutable global state.

  • Code comprehension

    Yes, and this is a major issue if you're expecting students to pick it up and understand it. If you want students to be able to understand and predict, or at least intuit with reasonable accuracy, the interactions between components, then those interactions need to be somehow structured.

  • Performance

    No, this probably doesn't apply to you. At least, I hope you're not expecting this design to work across multiple threads, because concurrency would exacerbate all the other problems mentioned above, before we worry about performance at all.

C++ static storage duration objects have another issue which isn't addressed in the other (language-agnostic) question: the order of dynamic initialization of non-local variables makes dependencies between them problematic.

Licencié sous: CC-BY-SA avec attribution
scroll top