Undefined reference to parent class constructor

There are two programs below. When I run the first program, I encounter an error stating undefined reference to base class constructor and destructor. However, when I run the second one, it successfully compiles. Am I right to assume that the base class constructor and destructor are synthesized in the second program, but not in the first one? Why is that so?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Base
{
  public:
    Base();
    virtual ~Base();
};

class Derived : public Base
{
  public:
    Derived();
    virtual ~Derived();
};

Derived::Derived(){};

Derived::~Derived(){};

int main
(
  int argc,
  char** argv
)
{
  return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base
{
  public:
    Base();
    virtual ~Base();
};

class Derived : public Base
{
  public:
    Derived(){};
    virtual ~Derived(){};
};

int main
(
  int argc,
  char** argv
)
{
  return 0;
}
Am I right to assume that the base class constructor and destructor are synthesized in the second program, but not in the first one?

Correct,

Base(); is a declaration for the 0-arg constructor of the Base class.

Base(){}; is the same as
1
2
3
4
Base()
{
    // empty
}

which is defining the 0-arg constructor of the Base class.

If you only declare a function but don't implement it, it's a linker error.

e.g.
1
2
3
4
5
6
void function_declared_but_not_defined(int apples);

int main()
{
    function_declared_but_not_defined(42);
}

will not build unless you either implement the function yourself, or link with another object file/library that did define it.

Starting in C++11, if you have an empty constructor, it is preferable to just say Base() = default; to define it.
Last edited on
I think I understand the linker error, what I don't understand is how come the second example does not cause a linker error, as it does not provide implementation for base class constructor or destructor, either.
the second example ... does not provide implementation for base class constructor or destructor, either.
Sorry, I completely missed the actual point of your question.

I assume it has something to do with the functions defined within the class body being implicitly inline, but I can't find a good source to confirm exactly why that works.
https://en.cppreference.com/w/cpp/language/inline
A function defined entirely inside a class/struct/union definition, whether it's a member function or a non-member friend function, is implicitly an inline function


This will also compile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Base
{
  public:
    Base();
    virtual ~Base();
};

class Derived : public Base
{
  public:
    inline Derived();
    inline virtual ~Derived();
};

Derived::Derived(){};

Derived::~Derived(){};

int main
(
  int argc,
  char** argv
)
{
  return 0;
}
Last edited on
I see, for the time being I will just stick the base class constructor and destructor with = default then. Thanks for your help :)
Someone more in tune with the specifics of the standard can probably explain this better, but essentially, this is all because of the One Definition Rule in C++.

https://en.cppreference.com/w/cpp/language/definition#One_Definition_Rule
Three key points:
(1) One and only one definition of every non-inline function or variable that is odr-used is required
(2) For an inline function, a definition is required in every translation unit where it is odr-used.
(3) Informally, an object is odr-used if its value is read (unless it is a compile time constant) or written, its address is taken, or a reference is bound to it


In your second example's (non-inline) Derived constructor, the Base class constructor is implicitly called before the body of the Derived constructor, so its definition needs to be known (item 1), because the Derived constructor is not inline itself.

But once you declare Derived as inline, it is now inline just like the Base constructor. Your program never calls the Derived constructor from anywhere, therefore the compiler is OK with a definition for it not existing, because the Derived constructor is not odr-used (item 2, item 3).
Last edited on
I think what you say makes sence, I tried calling Derived d in the main function, which I assume constitutes an "odr-use" of the default constructor. The constructor call throws a linker error and confirms what you said. However, if I replace Derived d with Derived d(), the error goes away! I think I understand the difference between the two: Derived d calls the default constructor; Derived d() value-initializes d, a process which should have also called the default constructor (besides value-initializing class members). My question is: I expected both calls to end in a linker error, but Derived d() doesn't. Why?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Base
{
  public:
    Base();
    virtual ~Base();
};

class Derived : public Base
{
  public:
    Derived(){};
    virtual ~Derived(){};
};

int main
(
  int argc,
  char** argv
)
{
  Derived d1(); //no error
  Derived d2; //error!
  return 0;
}
Last edited on
Derived d(); actually just declares a function called d1 that takes no arguments and returns a Derived class! So that's the only reason there is no error. To get rid of the linnker errors you need to define Base() and ~Base().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
};

class Derived : public Base
{
public:
    Derived() = default;
    ~Derived() = default;
    void hello() const { std::cout << "hello\n"; }
};

int main()
{
    Derived d1();
    //d1.hello();    // Error! It's a function, not a class.
  
    Derived d2;   // Okay.
    d2.hello();   // howdy!
}

Last edited on
> I tried calling Derived d in the main function, which I assume constitutes an "odr-use" of the default constructor.

In the original example, there is odr-use of the destructor whether we call it or not (it is virtual, but not pure virtual). However, for odr violations ,"no diagnostic is required"; the implementation may not encounter any use of the vtables for these classes in the program, and may not try to instantiate them.
Thank you gentlement, I think I finally understand it.
Topic archived. No new replies allowed.