Search code examples
c++pluginslinkerextern

Program which behavior changes depending on classes it is linked against


I do not think what I attempt is fancy enough to deserve the term "plugin" but here what I am trying to do:

Given files a.h, a.cpp and main.cpp, I would like to create other files such as:

g++ -o test main.cpp a.cpp b.cpp

results in a test program doing something, and

g++ -o test main.cpp a.cpp c.cpp

does something else.

This part I already have working, cf code below. My issue is: would it be possible to have

 g++ -o test main.cpp a.cpp

do some default behavior? I tried several things, but I always end up with something undefined.

Code I have so far:

// a.h    

#ifndef A_H_
#define A_H_

class A {
 public:
  A();
  ~A();
  virtual void print()=0;
};

#endif


// a.cpp

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

A::A(){}
A::~A(){}


// b.h

#include "a.h"

class B: public A {
 public:
  B();
  ~B();
  void print();
};


// b.cpp

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

B::B(){}
B::~B(){}
void B::print(){
  std::cout << "I am B" << std::endl;
}

A* a = new B();


//  code in c.h and c.cpp similar to the one in b.h and b.cpp
//   just "B" replaced by "C"

// main.cpp

#include "a.h"

extern A* a;

int main(){
  a->print();
}

When compiling against b.cpp, code prints "I am B", when compiling against c.cpp, code prints "I am C".

I would like:

g++ -o test main.cpp a.cpp

to have test either do nothing or do a default behavior. Does not need to be simple.


Solution

  • Here's a (non-portable) option using weak symbols.

    a.h

    struct A {
      public:
        virtual void print() = 0;
    };
    
    struct Dummy: A {
      void print() override {};
    };
    
    A* init();
    

    main.cpp

    #include "a.h"
    
    A* __attribute__((weak)) init()
    {
      return new Dummy;
    }
    
    int main()
    {
      A* a = init();
      a->print();
    }
    

    b.cpp

    #include "a.h"
    #include <iostream>
    
    struct B: A
    {
      void print() override {
        std::cout << "B" << std::endl;
      }
    };
    
    A* init()
    {
      return new B;
    }
    

    If you don't link with b.cpp or any other entity that provides an init function, the one in main.cpp will be used. If you link with b.cpp, that one's definition will be used.

    This sort of provides a "default implementation" for the init function, and lets you manage initialization by not using globals (not important here, but can get more tricky once you flesh out your plugin system).