Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gradually construct an object

Tags:

c++

Suppose there is a hierarchy of two classes (class Derived: public Base). Both these classes have big memory footprint and costly constructors. Note that nothing in these classes is allocated in heap: they just have a big sizeof.

Then there is a function with a fast path (executed always) and a slow path (executed conditionally). Fast path needs a Base instance, and slow path needs a Derived instance constructed from existing base. Also, slow path decision can be made only after the fast path.

Current code looks like this:

void f()
{
    Base base;
    /* fast path */

    if (need_slow_path) {
        Derived derived (base);
        /* slow path */
    }
}

This is inefficient, because the base needs to be copied into derived; also the base is allocated twice and there is a risk of overflowing the stack. What I want to have:

  • allocate memory for Derived instance
  • call Base ctor on it
  • execute the fast path
  • if needed, call Derived ctor on the existing Base instance and execute the slow path

Is it possible in C++? If not, what are possible workarounds? Obviously, I'm trying to optimize for speed.

like image 988
intelfx Avatar asked Mar 05 '14 07:03

intelfx


4 Answers

I am afraid this is not possible just as you wrote - any constructor of Derived must call a constructor of the Base subobject, so the only way to do that legally would be to call Base's destructor first, and I believe you don't want that.

However, it should be easy to solve this with a slight redesign - prefer composition over inheritance, and make Derived a separate class that will store a reference (in the general sense; it can of course be a pointer) to Base and use it. If access control is an issue, I feel a friend is justified here.

like image 105
Angew is no longer proud of SO Avatar answered Nov 03 '22 17:11

Angew is no longer proud of SO


You should change your design slightly to change your reliance on inheritance to that on composition.

You could encapsulate members of derived class (not present in the base class) into another class, and keep it's null reference in the derived class.

Now directly initialize derived class without initializing new class's object.

Whenever slow path is required, you can initialize and use it.

Benefits

  • Inheritance relationship between derived and base class is preserved.
  • Base class object is never copied.
  • You have lazy initialization of derived class.
like image 38
Tanmay Patil Avatar answered Nov 03 '22 16:11

Tanmay Patil


I can fake it.

Move/all the data of derived into an optional (be it boost or std::ts::optional proposal for post C++14, or hand rolled).

Iff you want the slow path, initialize the optional. Otherwise, leave it as nullopt.

There will be a bool overhead, and checks when you assign/compare/destroy implicit. And things like virtual functions will be derived (ie, you have to manage dynamic dispath manually).

struct Base {
  char random_data[1000];
  // virtual ~Base() {} // maybe, if you intend to pass it around
};
struct Derived:Base {
  struct Derived_Data {
    std::string non_trivial[1000];
  };
  boost::optional< Derived_Data > m_;
};

now we can create a Derived, and only after we m_.emplace() does the Derived_Data get constructed. Everything still lives is in one contiguous memory block (with a bool injected by the optional to track if m_ was constructed).

like image 7
Yakk - Adam Nevraumont Avatar answered Nov 03 '22 18:11

Yakk - Adam Nevraumont


Not sure if you can do exacactly what you want i.e execute "fast" path before second contructor but i think you use 'placement new' feature - manually call contructors based on need_slow_path predicate. i.e but that changes flow a little:

  • allocate memory for Derived instance
  • call Base or Derived ctor on it
  • execute the fast path
  • execute the slow path (if needed(

The example code

#include <memory> 
void f(bool need_slow_path)
{
    char bufx[sizeof(Derived)];
    char* buf = bufx;

    Derived* derived = 0;
    Base* base = 0;

    if (need_slow_path ) {
        derived = new(buf) Derived();
        base = derived;
    } else {
        base = new(buf) Base();
    }

    /* fast path using *base */

    if (need_slow_path) {
        /* slow path using *base & * derived */
    }

    // manually destroy
    if (need_slow_path ) {
        derived->~Derived();
    } else {
        base->~Base();
    }
}

Placement new is well described here: What uses are there for "placement new"?

like image 4
Zbigniew Zagórski Avatar answered Nov 03 '22 17:11

Zbigniew Zagórski