0

Say I have a fixed class Base that I cannot modify (e.g. in a static lib), which uses an internal data type in its constructor. I would like to provide a version of the Base class that uses a more generic data type instead but otherwise behaves the same way. So I want to write a Derived class that takes a different data type in its constructor and does the conversion before calling Base's constructor.

I see two ways of doing that and I would like to know if there is any preference to one or the other. Or if there is a third one that I missed.

  • Option 1 uses an initialization list. Since I need to call some conversion code before calling the Base's constructor, I wrote a static function that I can call from the initialization list.

  • Option 2 uses placement new to call Base's constructor at the end of Derived's constructor after I do the conversion directly.

Here's the code. I am using personal_int as a stand-in for the internal data structure that I want to encapsulate away. Derived uses int as a stand-in for the more generic data structure. The conversion occurs during Derived's constructor call.

#include <iostream>

using namespace std;

class Base
{
public:

    typedef int personal_int;
    
    Base() : m_i(0) {}
    Base(int i) : m_i(i) {}
    
    personal_int get_i() 
    {
        return m_i;
    }
    
protected:
    personal_int m_i;
};


class Derived1 : public Base
{
public:

    static const Base::personal_int multiply_n(int i, int n)
    {
        return static_cast<Base::personal_int>(i*n);
    }

    Derived1(int i) : Base(multiply_n(i, 2)) {}
    
    int get_i() 
    {
        return static_cast<int>(m_i);
    }
    
};

class Derived2 : public Base
{
public:

    Derived2(int i)
    {
        Base::personal_int pi = static_cast<int>(i*2);
        new (this) Base(pi);
    }
    
    int get_i() 
    {
        return static_cast<int>(m_i);
    }
};


int main()
{
    Derived1 d1(3);
    Derived2 d2(3);
    
    cout << d1.get_i() << endl;
    cout << d2.get_i() << endl;

    return 0;
}

These are the arguments I could find in favor of one option or the other:

Pro Derived1:

  • It avoids placement new (possible problems with deallocation in more complex cases?)
  • Base does not need an empty constructor. This could be a severe limitation for Derived2 if the empty constructor was missing in Base since I cannot modify the class in the first place
  • It might be less efficient to first initialize the data structure for the empty constructor call and then initialize it again during the placement new call.

Pro Derived2:

  • No need to split the conversion into a different function when all the derived class does is abstract the constructor of the base class

I think Derived1 is the superior (even more efficient?) one. So would there be any reason to go for Derived2? Or is there a third option that would be preferable?

Edit: Apparently, my question has caused some confusion because it is very simplified.

The reason I am using a special function multiply_n instead of putting the conversion directly in the initializer list is that in a more realistic scenario, I am not not simply converting from int to personal_int (which is essentially the same).

Instead, I could be converting between more complex data structures, e.g. from an xml string in Derived to a struct in Base. A conversion like this would span multiple lines of code and could not simply be placed in the initializer list.

Cerno
  • 751
  • 4
  • 14
  • Don't understand pro of `Derived2`, `Base(static_cast(i*2))` can be done for `Derived1` as you do for `pi`. no need of specific function (and function can be done for both too). – Jarod42 Oct 01 '21 at 09:28
  • You are using `Base::Base(int)` anyway, so `Derived(int i) : Base(i * 2) {}` works just fine. Notwithstanding that, `Base::personal_int` is the same type as `int` – Caleth Oct 01 '21 at 11:26
  • @Caleth This is a very simplified example to make the issue easier to grasp. A more realistic scenario might be replacing the `personal_int` by a nested `struct` with members of types int, float and string and replacing `int` by an xml string. Then `multiply_n` would turn into a more complex conversion function spanning multiple lines. – Cerno Oct 02 '21 at 00:16
  • @Jarod42 If I understand you correctly then that is only true if the `static_cast` and multiplication fits in a single command. For a more complex example spanning multiple lines of code, this would not true. That's why I need a separate function. I also don't really see the benefit in using `Derived2` either, but I was wondering whether there is some scenario where `Derived1` could become problematic, e.g. in case the `multiply_n` function becomes very complex, maybe in terms of stack size. – Cerno Oct 02 '21 at 00:23
  • If you want to have several statements, not separated in function, you might still use immediately called lambda: `Derived1(int i) : Base([]() {/*..*/ return pi; } /*-->*/()/*<--*/ ) {}`. See [how-to-immediately-invoke-a-c-lambda](https://stackoverflow.com/questions/44868369/how-to-immediately-invoke-a-c-lambda) – Jarod42 Oct 04 '21 at 09:25
  • @Jarod42 Thanks a lot for that hint, but I fear that's going to be pretty unreadable. I think I may prefer the function then. – Cerno Oct 04 '21 at 10:39

0 Answers0