21

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.

intelfx
  • 2,386
  • 1
  • 19
  • 32
  • "call Derived ctor on the existing Base" constructor can be called only once, you would have to make a method that does any extra initializations for slow path. – marcinj Mar 05 '14 at 07:50
  • look into C++ polymorphism – bolov Mar 05 '14 at 07:55
  • 1
    The first question is, of course, does it matter that much that the slow path is a bit slower ? If you are afraid of blowing up the stack, then use `auto const d = std::make_unique(base);` to allocate your derived instance on the heap. – Matthieu M. Mar 05 '14 at 08:12
  • 1
    Maybe composition would work for you somehow? So that you don't have an inheritance but just 1 class containing a pointer to an object of another class? – TT_ stands with Russia Mar 05 '14 at 18:28
  • Ah, composition was already suggested in the accepted answer. – TT_ stands with Russia Mar 05 '14 at 18:42

7 Answers7

32

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Same thing holds (as in my comment below). I've surely considered composition, but IIUC this would incur penalties caused by extra dereferences or register pressure. Are they negligible? – intelfx Mar 05 '14 at 08:05
  • 3
    @intelfx I am afraid the only real answer to what such a change's performance impact would be is: "profile it and see." – Angew is no longer proud of SO Mar 05 '14 at 08:08
  • Okay. I'm accepting this as it says clearly why the way I wanted isn't possible. Thanks @Angew, and i'll profile. – intelfx Mar 05 '14 at 08:21
11

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.
Mzf
  • 5,210
  • 2
  • 24
  • 37
Tanmay Patil
  • 6,882
  • 2
  • 25
  • 45
  • I've considered this. However, this is an extra dereference each time I want to access `Base` members. Or an extra register busy with the pointer. – intelfx Mar 05 '14 at 08:03
  • 5
    @intelfx: Have you tried it ? If you have not, how do you know exactly what will occur after the compiler dark magic has been applied ? I understand your gut feeling about it, but where optimizations are concerned, you should measure. – Matthieu M. Mar 05 '14 at 08:08
  • 1
    Then you could avoid the dereference by skipping class and having a function for lazy initialization. But the code could be difficult to maintain. I'd suggest to go for a maintainable code rather than super-optimized one. And I hope you were joking about one extra register. I'm sure it won't matter on a computer which can run a web browser to render StackOverflow. Cheers – Tanmay Patil Mar 05 '14 at 08:09
  • That's what I had thought, otherwise I was sure you wouldn't have worried about that level of optimization. In that case, a function for lazy initialization should do the job. Good luck. – Tanmay Patil Mar 05 '14 at 08:20
  • @TanmayPatil: Thanks. I've accepted Angew's answer as it is a bit more clear, but anyway. Upvoted both. – intelfx Mar 05 '14 at 08:25
  • Glad to know it helped. – Tanmay Patil Mar 05 '14 at 08:28
7

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).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Beautiful.. So are you saying that fields of Derived shall end up in Derived_Data, and methods of Derived shall remain in Derived but get all field accesses prefixed with `m_.`? – intelfx Mar 07 '14 at 21:13
  • @intelfx yes, but again nothing polymorphic can occur with said methods. I just have shown how to defer construction of some of the data. The class is always `Derived`. You could have compile time polymorphism via a `Base&` to the instance, I guess. – Yakk - Adam Nevraumont Mar 08 '14 at 00:22
4

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"?

Community
  • 1
  • 1
Zbigniew Zagórski
  • 1,921
  • 1
  • 13
  • 23
2

Can you define move copy con't in your compiler ?

There is an excellent explanation (although a bit long ) here

https://skillsmatter.com/skillscasts/2188-move-semanticsperfect-forwarding-and-rvalue-references

I don't have experience with move semantics so I might be wrong but since you want to avoid coping the base object when passing it to the derived class move semantics should do the trick

  • 1
    He says the objects do not have dynamic allocations but simply a big size on the stack. How will move semantics help you there? – heinrichj Mar 05 '14 at 07:57
  • It will help to avoid unnecessary memory coping when entering the copy constructor. currently we copy the base object into an temp variable (Rval) when using the derived contractor- move semantics avoid this unnecessary coping , although we remain with two copies of the base ,one for the base and one for the derived we don't need the transit stage to be copied – user3379224 Mar 05 '14 at 08:17
  • @user3379224 How? Stack-based data cannot be moved about in memory, it has to be copied. The only reason why moving e.g. a `std::vector` is faster than copying it is because it simply copies the pointer, thus logically "moving" the dynamic memory from one vector to another. But the stack-based parts (the pointers) are still *copied.* – Angew is no longer proud of SO Mar 05 '14 at 08:24
  • @user3379224 Why on earth would the derived ctor create a temporary copy? A simple `const Base&` will get rid of that. – Angew is no longer proud of SO Mar 05 '14 at 08:24
  • Angew - I might have not understood move semantics. As far as I understand one of the reasons for using move semantic is exactly this- to avoid the memory coping in the transition stage. If no move constructor is defined the base will be copied twice once to the rvale and then again to the new copy in derived class . Move contractor will copy once since it will use the memory allocated for the rvalue for the new derived class. – user3379224 Mar 05 '14 at 09:47
  • Angew - why not use a const Base& - I agree this is more simple but it puts constrains on what is done in the derived constructor (and I think it might be a problem regarding thread safety) – user3379224 Mar 05 '14 at 09:51
2

First extract constructor code into initializing methods both for Base and Derived.

Then I would make the code similar to this:

void f()
{
    Derived derived;
    derived.baseInit();
    /* fast path */

    if (need_slow_path) {
        derived.derivedInit();
        /* slow path */
    }
}

It's a good idea to extract classes and use composition as Tanmay Patil suggested in his answer.

And yet another hint: If you haven't done already, dive into Unit-Tests. They will help you dealing with huge classes.

TobiMcNamobi
  • 4,687
  • 3
  • 33
  • 52
  • Nice idea, but intuitively initialization code should stay in some constructor according to me. But this can easily solve OP's problem without much changes. (BTW it should be composition not aggregation). Regards – Tanmay Patil Mar 05 '14 at 08:15
  • This will only work if initialisation of all (or most) members involved can be delayed as well. – Angew is no longer proud of SO Mar 05 '14 at 08:16
2

Perhaps instead of a class and constructors, you need a plain-old-struct and initialization functions here. You’ll be giving up a lot of the C++ conveniences, of course, but you’ll be able to implement your optimization.

Robert Fisher
  • 578
  • 4
  • 20
  • This isn't substantively different. If you were about to write an "initialize" function taking fewer arguments, then just write a constructor taking fewer arguments. – djechlin Mar 05 '14 at 21:23