dimanche 26 juin 2016

Mystical downcast failure

It is a simple, template-less Factory design pattern implementation as part of a hobby project. The goal is to implement it as clean as possible, with the restriction that templates can't be used, and the library classes need to be able to be inherited virtually.

My problem is, that downcasting in Factory.cc:73 (in the line A* a = dynamic_cast<A*>(r);) doesn't work, but it should.

My factory.h:

#ifndef factory_h
#define factory_h

class IAmPolymorphicNow {
  private:
    virtual void iAmPolymorphicNow();

  protected:
    IAmPolymorphicNow();
};

namespace Factory {
  class Factory : private IAmPolymorphicNow {
  };

  class Mediator;

  class Result : private IAmPolymorphicNow {
    public:
      Result();
      Result(Mediator fm);
  };

  typedef void (*MakeMethod)(Factory* factory, Result* result);

  class Mediator {
    private:
      Factory* factory;
      MakeMethod makeMethod;

    public:
      Mediator(Factory* factory, MakeMethod makeMethod);
      void call(Result* result);
  };
};

#endif

My factory.cc (contains also the minimal test case):

#include <iostream>
#include "factory.h"

using namespace std;

void IAmPolymorphicNow::iAmPolymorphicNow() {
  // nothing now
};

IAmPolymorphicNow::IAmPolymorphicNow() {
  // nothing now
};

Factory::Result::Result() {
  cout << "Factory::Result::Result()\n";
};

Factory::Result::Result(Mediator fm) {
  cout << "Factory::Result::Result(Mediator)\n";
  fm.call(this);
};

Factory::Mediator::Mediator(Factory* factory, MakeMethod makeMethod) {
  cout << "Factory::Mediator::Mediator(Factory*, MakeMethod)\n";
  this->factory = factory;
  this->makeMethod = makeMethod;
};

void Factory::Mediator::call(Result* result) {
  cout << "Factory::Mediator::call(Result*)\n";
  (*makeMethod)(factory, result);
};

class A;

class B : public virtual Factory::Factory {
  private:
    int v;

  public:
    B(int v);
    int getV() const;
    static void makeCb(Factory* f, ::Factory::Result* a);
    ::Factory::Mediator make();
};

class A : public virtual Factory::Result {
  friend class B;

  private:
    int v;

  public:
    A();
    A(Factory::Mediator fm);
    int getV() const;
    void setV(int v);
};

B::B(int v) {
  cout << "B::B()\n";
  this->v = v;
};

int B::getV() const {
  cout << "B::getV()\n";
  return v;
};

void B::makeCb(Factory* f, ::Factory::Result* r) {
  cout << "B::makeCb(Factory*, Factory::Result*)\n";
  B* b = dynamic_cast<B*>(f);
  A* a = dynamic_cast<A*>(r);
  a->setV(b->getV()+1);
};

Factory::Mediator B::make() {
  cout << "Factory::Mediator B::make()\n";
  return ::Factory::Mediator(static_cast<Factory*>(this), &B::makeCb);
};

A::A() {
  cout << "A::A()\n";
  v = 0;
};

A::A(Factory::Mediator fm) : Factory::Result(fm) {
  cout << "A::A(Factory::Mediator)\n";
};

int A::getV() const {
  cout << "A::getV()\n";
  return v;
};

void A::setV(int v) {
  cout << "A::setV(" << v << ")\n";
  this->v = v;
};

int main(int argc, char **argv) {
  B b(42);
  A a = b.make();
  cout << "a.v = " << a.getV() << "\n";
  return 0;
}

How the problem can be reproduced:

$ g++ -o factory factory.cc -g -Wall -std=c++11
$ gdb ./factory
Reading symbols from ./factory...done.
(gdb) run
Starting program: ./factory
B::B()
Factory::Mediator B::make()
Factory::Mediator::Mediator(Factory*, MakeMethod)
Factory::Result::Result(Mediator)
Factory::Mediator::call(Result*)
B::makeCb(Factory*, Factory::Result*)
B::getV()
A::setV(43)

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400da6 in A::setV (this=0x0, v=43) at factory.cc:98
98        this->v = v;
(gdb) bt
#0  0x0000000000400da6 in A::setV (this=0x0, v=43) at factory.cc:98
#1  0x0000000000400b7a in B::makeCb (f=0x7fffffffe610, r=0x7fffffffe600) at factory.cc:74
#2  0x0000000000400a08 in Factory::Mediator::call (this=0x7fffffffe590, result=0x7fffffffe600) at factory.cc:31
#3  0x0000000000400990 in Factory::Result::Result (this=0x7fffffffe600, fm=...) at factory.cc:20
#4  0x0000000000400d0e in A::A (this=0x7fffffffe600, fm=..., __in_chrg=<optimized out>, __vtt_parm=<optimized out>) at factory.cc:87
#5  0x0000000000400ded in main (argc=1, argv=0x7fffffffe718) at factory.cc:103
(gdb) frame 1
#1  0x0000000000400b7a in B::makeCb (f=0x7fffffffe610, r=0x7fffffffe600) at factory.cc:74
74        a->setV(b->getV()+1);
(gdb) print a
$1 = (A *) 0x0
(gdb) print b
$2 = (B *) 0x7fffffffe610
(gdb) list
69
70      void B::makeCb(Factory* f, ::Factory::Result* r) {
71        cout << "B::makeCb(Factory*, Factory::Result*)\n";
72        B* b = dynamic_cast<B*>(f);
73        A* a = dynamic_cast<A*>(r);
74        a->setV(b->getV()+1);
75      };
76
77      Factory::Mediator B::make() {
78        cout << "Factory::Mediator B::make()\n";

Note:

  1. The question is "Why is it unable to downcast?", and not "Which stl/boost/... template should I use?".
  2. This code is a minimal sanitized example. For ask, I will be glad to further shorten it, but afaik it will be already on the border on the obfuscation.

Aucun commentaire:

Enregistrer un commentaire