2

Say I have a superclass that, when it initializes, wants to run some code that relies on a whole bunch of class variables that may or may not be overridden by a subclass in its constructor.

What's the accepted, clean way to code that?

I feel like I'm having a brain fart; this should be a standard, beginner usage of inheritance, but I can't figure it out.

e.g. say I have a superclass that represents a vehicle, and when it starts, I want to do a whole bunch of code where it processes, say, the load per axle or something (doesn't matter) but that code uses as inputs a bunch of parameters that exist for all vehicles (and thus exist in the superclass), say weight, length, numwheels, numaxles, maybe even complicated data structures defining how many wheels per axle, etc.).

The various subclasses (sportscar, bigrig, motorcycle), want to set the weight, length, numwheels, numaxles, etc. before the superclass does its processing.


Super::Super() {
    Process(var1_,var2_,var3_,var4_, ...);
}

Sub1::Sub1(): Super() {
    var1_ = <some math>;
    var2_ = <some math>;
    ...
}

doesn't work because the superclass Process() runs before the vars get set by the subclass. Right?


Super::Super(float var1, WackyDatastructureDef var2, int var3, WackyStruct2 var4, ...),
var1_(var1), var2_(var2), var3_(var3), ............... {
    Process(var1_,var2_,var3_,var4_, ...);
}

Sub1::Sub1(): Super(<some math>, <some math>, <some math>, <some math>, ......) {
    ....
}

looks horrible for obvious reasons. Also, it looks like a pain if I only need to override 2 out of the 20 default variable values.


Super::Super() {}
void Super::Init() {
    Process(var1_, var2_, var3_, var4_ ...... );
}

Sub1::Sub1(): Super() {
    var1_ = <some math>;
    var2_ = <some math>;
    ...

    Init();
}

looks the cleanest but I don't like it... it's weird to have to remember to call Init() at the end of all my subclass constructors. What if another programmer wants to subclass off my superclass and doesn't know my magic rule?


What's the right way to do this?

RibsNGibs
  • 63
  • 3
  • 2
    What about a **public** Init() method? Your constructors will peform only variables initialization, you shouldn't put any logic inside them. It's almost universally known and it won't astonish anyone. You can even _hide_ it in a factory class that both will build object and initialize it. – Adriano Repetti Jan 14 '14 at 08:29
  • I've read Init methods are bad (it looks like people are calling that pattern "two-phase construction"). Do you have an opinion on that? I will look up factory construction - I heard of it way back in CS class in university; I'm not familiar with it. – RibsNGibs Jan 14 '14 at 08:54
  • This is a long old discussion. Yes, they're not a good thing if you can avoid them. They're just an **option** to use when another solution is worse (as you thought for example when you may have a long list of parameters in your constructors). – Adriano Repetti Jan 14 '14 at 09:06

2 Answers2

2

There are many ways to solve this issue (lack of virtual constructors in C++). Each one has its own benefits and drawbacks. Most common patterns to workaround this limitation:

  • Pass all required arguments to base class constructor. This can be really annoying if you need more than few parameters. Code will be less and less readable and pretty hard to extend if requirements change. Of course it has a big benefit: it's not a workaround and everyone will understand it.

  • Change your design (this may be the best thing to do but it may require a lot of work). If you need a lot of parameters then you may pack all arguments in separate class, it'll hold object status. Base class constructor will just accept one parameter of this type and it'll contain its status (or just its initialization data but this is another story). Its benefit is to keep design clear (no workaround like for first solution) but it may involve some complexity if this initialization token will evolve with its own class hierarchy.

  • Add a public initialization method. Change your Init() method to public, it won't be invoke by derived constructors but by class users. This will allow you to add initialization code in each derived class (and initialization order is then controlled by implementation itself). This method is pretty old school and it requires users will call it but it has one big benefit: it's universally known and it won't astonish anyone. See this post here on SO for a small discussion about them.

  • Virtual constructor idiom. See this article for a reference. It works as intended and you can make it easier to use with few template methods. IMO biggest disadvantage is that it changes how you manage inheritance and initialization when you create a new derived class. This may be boring and error prone and prolix. Moreover you change how a class is instantiated too and, for me, this is always annoying.

Few notes about second solution (from comments). If you apply this I see at least these options:

  • Stupid entity (just data, no logic) that holds all required parameters.

  • Encapsulate object status in a separate object. Object you pass from derived classes is not used and dropped but it'll be part of object.

In both cases you can have or not a parallel hierarchy for parameters (BaseParametersHolder, DerivedParametersHolder and so on). Please note that holder doesn't suffer from same problem of first solution (many arguments) because creation can be delegated to a private function (example is to illustrate concept, code is far to be nice):

class Derived : public Base 
{ 
public:
    Derived() : Base(CreateParameters()) 
    {
    }

private:
    ParameterHolder CreateParameters()
    {
        ParameterHolder parameters;
        parameters.Value = 1;
        parameters.AnotherValue = 2;

        return parameters;
    }
};

What to use? There is not an answer. I'd prefer to be consistent across code (so if you decide to use holders then use them everywhere, do not mix - for example - with v.i. idiom). Just pick proper one each time and try to be consistent.

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • 1
    -1 `init` methods are generally evil. A good reference for their evilness is Bjarne's discussion in the appendix to The C++ Programming Language 3rd edition. I think it was Coplien who used the term "virtual constructor", then about clone method. That's in the C++ FAQ. The linked to article's code is an abomination, really really bad. – Cheers and hth. - Alf Jan 14 '14 at 08:54
  • @Cheersandhth.-Alf init methods are **not evil by definition**. There are situations where they're evil. There are situations where they're **less evil than something else** (a constructor with 10 parameters, for example). They're just a tool to pick cum grano salis, when they're better than something else (that's why I would avoid an arbitrary sentence like "use this, do not use that"). – Adriano Repetti Jan 14 '14 at 08:56
  • Hi! Thanks - I don't like #1 because too many arguments is ugly. I'm not super happy with #3 (Init()) because even I'm already forgetting to call my Init functions in the right place. I kind of like #2; packing everything in a separate class. Can you elaborate? Is the idea that the base class has an associated virtual "parameterHolder" class, and that each sub class would have its own associated subParameterHolder class (whose constructor sets all 20 parameters), and then I pass that to the base constructor? – RibsNGibs Jan 15 '14 at 19:05
  • 1
    @RibsNGibs you can do it in (at least) two ways. 1) this parameters class is a _stupid_ entity. No derived classes, just hold what base class needs. 2) it has its own class hierarchy (parallel to implementation hierarchy). In this second case you have other options: a) use it as container for parameters then drop it. b) keep object state inside it but keep it private. c) keep object state inside it and make it public. Users will inspect object state through that class and they'll use it to create implementation classes. – Adriano Repetti Jan 15 '14 at 19:48
  • I can't say what's better, IMO it depends on context and for sure there are even more and better alternatives (I'm thinking for example to delegate these stuff to a separate class that will manage algorithm to setup initial object state, it'll receive a fully initialized object). – Adriano Repetti Jan 15 '14 at 19:52
  • Unless I am mistaken, #1 (stupid entity, just holds parameters) suffers from the same problem my original case had, where you would need a ton of stuff in your subclass constructor: `Sub::Sub() : Base(ParameterHolder(10,15,"Hello",78,12,...)){}`. Whereas if you have derived class, you can have `Sub::Sub() : Base(SubParameterHolder()){}`, and then in the SubParameterHolder class constructor you can set all your variables. Does that seem correct to you? – RibsNGibs Jan 15 '14 at 21:21
  • @RibsNGibs no, you can do this: `class Derived : public Base { public: Derived() : Base(CreateParameters()) { } private: ParameterHolder CreateParameters() { ... } };`. Object creation is delegated to a private function (or from Users, if applicable) then you may not need a lot of arguments. – Adriano Repetti Jan 15 '14 at 23:10
  • @RibsNGibs I tried to make it more clear editing answer with that code. – Adriano Repetti Jan 15 '14 at 23:19
  • Awesome - to me this "pack all parameters in a Holder class and have virtual baseclass and subclass CreateParameters()" idea is the cleanest for what I have. subclass CreateParameters() calls baseclass CreateParameters() to generate a Holder object which has all the defaults in it, then overrides as many or few of the parameters as it needs. One last question: is there any problem with calling Base::CreateParameters() or Sub::CreateParameters() before the constructors have finished running? It works, but worried about unpredictable behavior. No static virtual functions in C++ apparently. – RibsNGibs Jan 16 '14 at 02:31
  • I'm keeping `init` method proposal downvote (bad advice), but above indicates that a downvote is not enough to educate. Bjarne Stroustrup discusses the evils and non-advantage of an `init` function in TCPPPL appendix E ["Standard-library exception safety"](http://www.stroustrup.com/3rd_safe.pdf) . The C++ FAQ has a short nice discussion of the **[Named Parameters Idiom](http://www.parashift.com/c++-faq/named-parameter-idiom.html)** for passing info as a `struct` more conveniently. – Cheers and hth. - Alf Jan 16 '14 at 03:39
  • Re the derived class problems, I discuss how to extend NMI, e.g. for derived classes, in ["\[cppx\] How to do typed optional arguments in C++98"](http://alfps.wordpress.com/2010/05/19/cppx-how-to-do-typed-optional-arguments-in-c98/), which refers further to Dr. Dobbs Journal article about it (also by me). In C++11 such a scheme can be greatly simplified. Unfortunately, AFAIK nobody's done that yet. – Cheers and hth. - Alf Jan 16 '14 at 03:42
  • @RibsNGibs no, I wouldn't use virtual functions for this stuff. IMO is not _nice_ to call virtual functions in constructor (even when you know what you're doing). If it's possible to avoid... – Adriano Repetti Jan 16 '14 at 08:16
  • @Cheersandhth.-Alf I feel more comfortable to list options than to point a single solution without discussions, OP is smart enough to read all and to decide what's better for him in his context. BTW thank you for links and authoritative references, that's **real** added value (if you did write that in your answer instead of a single - comment style - sentence I didn't post mine and I'd happy upvote it). – Adriano Repetti Jan 16 '14 at 08:27
0

Pass the relevant information up to the base class constructor.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Is what I did in my second code example what you mean? What if there are a lot of variables, or if enough work is done in figuring out those variables? That can make the superclass constructor declaration/definition be super gigantic and unwieldy, and the subclass constructor initialization list gigantic as well... – RibsNGibs Jan 14 '14 at 08:39
  • @RibsNGibs: I didn't say anything about how to pass the information. As you correctly observe, the way employed in your example #2 is quite impractical. Can you think of -- anything? -- that can help? – Cheers and hth. - Alf Jan 14 '14 at 08:41
  • yes :) you can even equip that with defaults and setters. that is called the [Named Parameter Idiom](http://www.parashift.com/c++-faq/named-parameter-idiom.html), and is a FAQ. It's often a good idea to read the FAQ. – Cheers and hth. - Alf Jan 14 '14 at 08:46
  • At first I thought perhaps a gigantic struct, but no, I can't think of anything obvious that doesn't involve at least a huge line in the subclass constructor initialization list where I generate the data to pass up to the superclass constructor. Is there some c++ mechanism I don't know about? – RibsNGibs Jan 14 '14 at 08:47
  • You can always name things. Re whether there is mechanism that you don't know about ("Is there some c++ mechanism that I don't know about"), that's difficult to say without telepathy. Which, I'm sad to say, doesn't work. – Cheers and hth. - Alf Jan 14 '14 at 08:49
  • @anonymous downvoter: don't you feel ashamed, not daring to say what you think? – Cheers and hth. - Alf Nov 21 '14 at 22:28