0

Here is the problem:

I have a class called Object, whose constructor accepts a std::function like this:

#include <functional>
#include <iostream>
#include <memory>
#include <string>

class Object {
 public:
  Object(std::function<void(int param)> f) : func(f) {}
  ~Object() { func(0); }
 private:
  std::function<void(int param)> func;
};

Then an abstract base class and several derived classes like this:

class AbstractBase {
 public:
  AbstractBase() {
    // How to initialize object.
  }
  virtual std::string toString() const = 0;

 private:
  Object object;
};

class Derived1 : public AbstractBase {
 public:
  std::string toString() const override { return "Derived1"; }
}

class Derived2 : public AbstractBase {
 public:
  std::string toString() const override { return "Derived2"; }
}

I am trying to initialize the object in AbstractBase like this:

AbstractBase()
    : object([this](int param) {
        // do something
        std::cout << toString() << std::endl;
        // do something
      }) {}

It compiles successfully, but raises "pure virtual method called" when AbstractBase is deleted. So how can I initialize object in the AbstractBase and make sure toString from derived class is called in the std::function?

is.magl
  • 315
  • 1
  • 3
  • 8
  • Please provide a [mcve]. Are you trying to use the `object`'s function on `this` in `~AbstractBase()`? – Barry Jul 21 '16 at 20:47
  • Think about when the `AbstractBase` destructor runs. – Kerrek SB Jul 21 '16 at 21:01
  • Updated the question. The desired behavior is that `Object` is used to capture some information from `Derived1` and `Derived2` and when `Object` is destructed, the collected information is saved (or in this case print to stdout). – is.magl Jul 21 '16 at 21:04
  • At the point of the call, your object is no longer a `Derived2`. Therefore the `Derived2::toString()` isn't called. The obvious solution is to not call virtual functions inside constructors or destructors (which is a sensible advice not only in C++) – milleniumbug Jul 21 '16 at 21:06

2 Answers2

2

The problem here is order of destructors calls. Here is a simple example:

#include <functional>
#include <iostream>
#include <memory>
#include <string>
using namespace std;

class Object {
 public:
  Object(){ cout<<"O\n"; }
 ~Object() { cout<<"~O\n"; }
};

class AbstractBase {
 public:
  AbstractBase(){ cout<<"A\n"; }
 ~AbstractBase() { cout<<"~A\n"; }

 private:
  Object object;
};

class Derived1 : public AbstractBase {
 public:
 Derived1() : AbstractBase()
 { cout<<"1\n"; }
 ~Derived1() { cout<<"~1\n"; }
};

class Derived2 : public AbstractBase {
 public:
 Derived2() : AbstractBase()
 { cout<<"2\n"; }
 ~Derived2() { cout<<"~2\n"; }
};

int main() {
    Derived1 d1;
    Derived2 d2;

    return 0;
}

And the output:

O
A
1
O
A
2
~2
~A
~O
~1
~A
~O

As you can see Objects destructors are called after Derived* destructors, so, in your code, Object is trying to call already destructed Derived* method.

stryku
  • 722
  • 5
  • 12
0

The C++ standard forbids calling a pure virtual method from either a constructor or a destructor.

If you were to directly call toString() from ~AbstractBase() you would almost certainly get a compiler error.

Your use of a lambda is basically "sneaking" the disallowed behaviour past the compiler.

Instead, you need to move the responsibility up to Derived - consider something like this:

struct Object {
     Object(decltype(func) f) : func(f) {};
     ~Object() { func(); }
   private:
     std::function<void()> func; //removed param since it's unused in your example
};

struct AbstractBase {
    A() = delete;
  protected:
    A(Object&& o) : obj(o) {} //we'll be using an rvalue in this example, be mindful of object lifetime in other usages
  private:
    Object& o;
};

struct Derived : AbstractBase {
    Derived() : AbstractBase(Object{[this]() { writestr(); }}) {}
  private:
    void writestr() { std::cout << "yes"; }
};

int main() {
    Derived d;
    return 0;
}
Olipro
  • 3,489
  • 19
  • 25