3

I have a virtual class A with data val and val2. val is set by A, but val2 is supposed to be set by children of A (based on the value of val). I would like to force every deriving class to set val2. The following

#include<iostream>

class A {
  public:
    A(): val(1), val2(getVal2())
    {};

    int val;
    int val2;

    protected:
      virtual int getVal2() = 0;
  };

class B: public A {
  protected:
    virtual int getVal2() { return 2*val; };
};

int main(){
  B b;
  std::cout << b.val2 << std::endl;
}

does not work since the constructor of A calls a function (getVal2) which at the time isn't defined yet:

/tmp/cc7x20z3.o: In function `A::A()':
test9.cpp:(.text._ZN1AC2Ev[_ZN1AC5Ev]+0x1f): undefined reference to `A::getVal2()'
collect2: error: ld returned 1 exit status

What's a better way of forcing deriving classes to set val2 explicitly?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • It's not that `B::getVal2()` isn't "defined yet" (it is); it's that `A::getVal2()` is being invoked instead because the `A` subobject is still undergoing construction. – Lightness Races in Orbit Jun 15 '15 at 15:08
  • 1
    You don't need semicolons `;` at the end of function definitions like you did for A's constructor and B's method `getVal2()`. – aslg Jun 15 '15 at 15:12

2 Answers2

8

Make val2 a parameter in the constructor of your base class (and don't use a default constructor):

class A
{
  public:
    A(int _val2) : val(1), val2(_val2) {};

    A() = delete;     //for clarity, not required as it is implicitly deleted

    //...
    int val;
    int val2;
};

This requires derived classes to set val2 in their constructors:

struct B : public A
{
    B() : A(0) {}   //A must be initialized, thus val2 is set in any case
    // ...
};

Don't try to access pure virtual members in the base class constructor: they are not yet constructed and thus not yet accessible; this yields undefined behaviour.


EDIT: from the comments it seems as if the problem were actually more complex than described in the OP. Namely: val shall be usable in the derived class constructor initializer list (and shall not be static).

Imo the cleanest solution I came up with (for others see the comments) is to introduce another base class and derivevirtual:

struct Abase
{
    Abase() : val(1) {}
    int val; 
};

struct A : virtual Abase
{
    A(int _val2) : val2(_val2) {}
    int val2;
};

struct B : virtual A
{
    B() : /* Abase() is called implicitly here, */ A(2*val){}  
                                                 //^^^^^ now val is correctly initialized.
};

DEMO.

Further, you can consider to inherit protected (since at least Abase is an implementation detail and is not meant to be used polymorphically).

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • In the constructor of `B`, how to I make sure that `val` (which is used in `B::getVal2()` is initialized _before_ `getVal2()` fires? – Nico Schlömer Jun 15 '15 at 15:11
  • @Nico Schlömer: place `val` before `val2` in your base class (see [here](http://stackoverflow.com/questions/2669888/c-initialization-order-for-member-classes)). – davidhigh Jun 15 '15 at 15:11
  • Can you show how `val` can be used for constructing `_val2`? – Nico Schlömer Jun 15 '15 at 15:42
  • Hm, I don't think so: `val*2` is executed _before_ `A`, and hence `val` is initialized. – Nico Schlömer Jun 15 '15 at 15:56
  • That is not *that* easy (though it works). The reason for this is that when you call `A(val*2)` in your base class (to set `val2`), it first evaluates all parameters (and at this time `val` is not initialized yet). Easy solution: set `val2` in the body of `B`'s constructor. (I'll think about others). – davidhigh Jun 15 '15 at 16:00
  • Setting it in the body would take away the argument from `A`'s constructor, meaning that derived classes are no longer forced to set `val2`. – Nico Schlömer Jun 15 '15 at 16:02
  • @NicoSchlömer: in fact, they are. But possibly this set must be corrected. Workarounds: (i) make val `static`. (ii) use a constructor of A with both parameters, val and val2. In the derived class, set val and use the same for val2. (iii) multiple inheritance: separate val and val2 and derive from both classes. Initialize that with val first. (probably there are more. I'll make an edit if you want it more clearly.) – davidhigh Jun 15 '15 at 16:08
  • I won't be able to set `val` in the derived class since it requires a substantial body of code to get there, and I don't want to replicate that for every derived class. Multiple inheritance sounds like an option, though one that's more complicated that what I had hoped for. – Nico Schlömer Jun 15 '15 at 16:13
  • @NicoSchlömer: so all `val` variables in A seem to have one common value. Thus you can consider to make it `static` (that works if it can be set a compile-time constant or filled by a `constexpr` function). – davidhigh Jun 15 '15 at 16:18
  • Unfortunately not: `val` depends on other constructor arguments of `A` which I haven't included in the example to make sure it's not overloaded. I realize now that the problem is more complex than I initially thought. – Nico Schlömer Jun 15 '15 at 16:21
  • @NicoSchlömer: next idea: use another base class `Abase` of `A` which contains `val`, and derive `B` `virtual`ly. This you are enforced to explicitly call the `Abase` as well as the `A` constructor in `B`. And for the `A` constructor, you can use `Abase::val`. (This is probably the cleanest solution). So tell me which alternative I shall edit in. – davidhigh Jun 15 '15 at 16:21
0

The fact that val may be used in the initialization of val2 complicates matters substantially. One solution would be to pass val2 to the constructor of A and create a parent class Abase that hosts val and makes sure that it gets initialized first in the chain.

Another option is to provide a purely virtual getter for val2 in A:

#include<iostream>

class A {
  public:
    A(): val(1)
    {};

    int val;

    virtual int getVal2() = 0;
  };

class B: public A {
  public:
    virtual int getVal2() { return 2*val; };
};

int main(){
  B b;
  std::cout << b.getVal2() << std::endl;
}
Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • Although it's your own question, I have the feeling your answer doesn't answer it. As I understood, the core of your question was how to workaround the false `val2(getVal2())` call in `A`. In your answer, however, you just drop that passage. – davidhigh Jun 17 '15 at 13:48
  • Thanks for the feedback! You're right that this doesn't force every deriving class to set `val2`. However, this is a simple workaround that achieves the same effect, namely that every deriving class provides `val2` (although through a getter and not the data directly -- too bad). Since this is simple enough and fulfills all other requirements (e.g., that `val` can be used for computing `val2`), I thought I'd post it. – Nico Schlömer Jun 17 '15 at 14:02