1

I am trying to use abstract classes and I met some problems when defining constructors of derived class. I wrote the following code, based on the answer to this question.

#include <string>
#include <iostream>

class ICommand {
private:
  ICommand();
public:
  const std::string name;
  ICommand(const std::string& name) : name(name) { }
  virtual void callMe(); 
  virtual void callMe2();
};


class MyCommand : public ICommand {
public:
  int x;
  MyCommand(const std::string& name) : ICommand(name) { }
  MyCommand(const std::string& name, int x) : ICommand(name), x(x) { }
  void callMe() { 
    std::cout << name << "\n"; 
  }
  void callMe2() { 
    std::cout << name << x << "\n"; 
  }
};

void f(std::string name) {
  MyCommand A(name);
  A.callMe();
}

This compiles without error. However my aim is to build a .so for a R package). In the R installation process, the .so is build without error, with clang++ -shared, but then there is a verification step which produces

unable to load shared object '/path/object.so':
/path/object.so: undefined symbol: _ZTI8ICommand

I've met this kind of problem before, and there are workarounds — not calling Icommand(name) is fairly simple, but I want to understand what is happening there, and, if possible, how to avoid the workaround.

Thanks in advance for your thoughts.

Answer

For the convenience of future readers: the only necessary change here is to replace the definition of virtual functions in the abstract class by

  virtual void callMe() = 0; 
  virtual void callMe2() = 0;

which makes them pure virtual functions. Why this settles the problem totally beats me.

Elvis
  • 548
  • 2
  • 14
  • You need to provide a definition for the `ICommand` default constructor. – G.M. Jul 23 '17 at 11:24
  • @Ron I don't have the code (it's part of the R installation process) but the fact is that the shared object does not seem correct. – Elvis Jul 23 '17 at 11:27
  • 1
    @Ron Where? Sorry if I'm missing something but I only see a declaration for the default ctor not a definition. – G.M. Jul 23 '17 at 11:28
  • @Elvis Can you try removing the declaration of the default constructor of `ICommand`? It is deleted by default because your constructor deletes it. – Rakete1111 Jul 23 '17 at 11:30
  • @G.M. I am sorry but I don't get the problem, I'am trying to call `ICommand(std::string)` which is defined. Can you be more precise? (e.g. giving a code modification) – Elvis Jul 23 '17 at 11:31
  • @Rakete1111 you mean the private constructor? I tried (as I don't get what its use is supposed to be, but in the answer to the linked question they seemed to insist on its importance). This doesn’t change anything. – Elvis Jul 23 '17 at 11:33
  • @Elvis There is no use to it. The default constructor is implicitly deleted because you have another constructor taking some parameter, it serves no use except maybe to be more explicit that the default constructor is not supposed to be called. – Rakete1111 Jul 23 '17 at 11:35
  • @Rakete1111 What you’re saying makes sense to me. However, quoting the linked question: "One option is to disable the default constructor for ICommand, so that subclasses will have to implement a constructor that calls your ICommand constructor" – Elvis Jul 23 '17 at 11:36
  • 1
    @Elvis "If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted " from [`[class.ctor]/4`](http://eel.is/c++draft/class.ctor#4) -> No implicit default constructor for classes that constructors with any parameters – Rakete1111 Jul 23 '17 at 11:41
  • @Rakete1111 ok, anyway, deleting this line does not settle the problem – Elvis Jul 23 '17 at 11:42
  • On a similar theme you've also declared `callMe` and `callMe2` virtual members for `ICommand` but haven't provided definitions for those. Either make them pure virtual or provide a suitable (empty?) definition. – G.M. Jul 23 '17 at 11:46
  • One more reason to just **hate** C++. I mean, "undefined symbol" because of a missing "= 0", seriously... – Delgan Mar 16 '18 at 09:20

2 Answers2

2

With the:

class MyClass {
private:
    MyClass();
};

You are deleting a default constructor. If you want to call a default constructor then (declare or) define or don't define one but don't delete it. Your derived class default constructor will call the base class default constructor:

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

class ICommand {
public:
    std::string name;
    ICommand() : name("The name") { std::cout << "Default base class constructor." << std::endl; }
    virtual void callMe() = 0;
};

class MyCommand : public ICommand {
public:
    MyCommand(){ std::cout << "Default derived class constructor." << std::endl; };
    void callMe() override {
        std::cout << name << std::endl;
    }
};

void f2(const std::string& name) {
    std::shared_ptr<ICommand> p = std::make_shared<MyCommand>();
    p->callMe();
}
int main(){
    f2("asdasd");
}

Part 2:
If you are trying to use the above classes in a polymorphic way then make your ICommand member functions pure virtual:

virtual void callMe() = 0; 
virtual void callMe2() = 0;

Modify the void f function to:

void f(const std::string& name) {
    std::shared_ptr<ICommand> p = std::make_shared<MyCommand>(name);
    p->callMe();
}

Live example on Coliru.

Ron
  • 14,674
  • 4
  • 34
  • 47
  • Wow, I am bluffed. All I have to add in the code is the `= 0` for the virtual functions, which in fact does not concern the initializer... (the error message does not help here!). I don’t get it! But it works! If you have some time to explain further, I'll be happy. – Elvis Jul 23 '17 at 13:09
1
class ICommand {
private:
  ICommand() = default;
public:
  const std::string name;
  ICommand(const std::string& name) : name(name) { }
  virtual ~ICommand() = default;
  virtual void callMe() = 0; 
  virtual void callMe2() = 0;
};