Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Not default destructor causes incomplete type error

This example shows strange behavior of compilers (msvc14, gcc, clang), but I did't find explanation.

When we implement pipml idiom and use forward declaration we need to consider that unique_ptr has own specific behavior with incomplete types. This cases was mentioned here and here.

But when we move definition of forwarded class to another header file and include headers in one place later with usage of client class, compilers become insane - in some special cases of destructor declaration they says about incomplete type.

Here is an minimal example. If uncomment "#define CASE_2" or "#define CASE_3" and try to build it, there will be compilation error.

file foo.h

#ifndef FOO_H
#define FOO_H

class Foo{};

#endif // FOO_H

file base.h

#ifndef BASE_H
#define BASE_H

#include <memory>

//#define CASE_1
//#define CASE_2
//#define CASE_3

class Foo;

class Base
{
public:

#if defined(CASE_1)
    ~Base() = default; // OK!
#elif defined(CASE_2)
    ~Base() {}; // error: invalid application of 'sizeof' to incomplete type 'Foo'
#elif defined(CASE_3)
    ~Base(); // error: invalid application of 'sizeof' to incomplete type 'Foo'
#endif

    // OK!

private:
    std::unique_ptr<Foo> m_foo;
};

#endif // BASE_H

file base.cpp

#include "base.h"

#if defined(CASE_3)
Base::~Base()
{
}
#endif

file main.cpp

#include "foo.h"  // No matter order of this includes
#include "base.h" //

int main()
{
    Base b;
}
like image 602
degreeme Avatar asked Dec 04 '25 08:12

degreeme


1 Answers

I believe, it has to do with C++ standard 12.4 / 6.

A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used (3.2) to destroy an object of its class type (3.7) or when it is explicitly defaulted after its first declaration.

When you have your destructor defaulted, it would only be defined when ODR-used, i.e. when Base object is destroyed. In your code snippet, no object of such type is ever destroyed, and thus, the program compiles - since deleter of unique_ptr is not actually called anywhere - it is only called by Base destructor, which is not defined in this scenario.

When you provide a user-defined destructor, it is defined in-place, and program becomes ill-formed, since you can't destruct a unique_ptr object of incomplete type.

By the way, having destructor declared, but not defined (as in ~base();) does not yield compilation error for the same reason.

like image 138
SergeyA Avatar answered Dec 05 '25 23:12

SergeyA



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!