Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
701 views
in Technique[技术] by (71.8m points)

c++ - unique_ptr, pimpl/forward declaration and complete definition

I already checked out the questions here and here, but still cannot figure out what is wrong.

This is the calling code:

#include "lib.h"

using namespace lib;

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

This is the lib code:

#ifndef lib_h
#define lib_h

#include <string>
#include <vector>
#include <memory>

namespace lib
{

class Foo_impl;

class Foo
{
    public:
        Foo();
        ~Foo();

    private:
        Foo(const Foo&);
        Foo& operator=(const Foo&);

        std::unique_ptr<Foo_impl> m_impl = nullptr;

        friend class Foo_impl;
};

} // namespace

#endif

clang++ gives me this error:

invalid application of 'sizeof' to an incomplete type 'lib::Foo_impl'
note: in instantiation of member function 'std::default_delete::operator()' requested

You can see I already specifically declared Foo destructor. What else am I missing here?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The implementation of Foo_impl must be complete prior to the instantiation required in std::unique_ptr<Foo_impl> m_impl = nullptr.

Leaving the type declared (but not initialised) will fix the error (std::unique_ptr<Foo_impl> m_impl;), you would then need to initialise it later on in the code.

The error you are seeing is from the implementation of a technique used to test for this; the incomplete type. Basically, sizeof will result in an error with types that are only forward declared (i.e. lack definition when used at that point in the code/compilation).

A possible fix here would look like;

class Foo_impl;

class Foo
{
  // redacted
  public:
    Foo();
    ~Foo();

  private:
    Foo(const Foo&);
    Foo& operator=(const Foo&);

    std::unique_ptr<Foo_impl> m_impl;// = nullptr;
};

class Foo_impl {
  // ...
};

Foo::Foo() : m_impl(nullptr)
{
}

Why is the complete type required?

The instantiation via = nullptr uses copy initialisation and requires the constructor and destructor to be declared (for unique_ptr<Foo_impl>). The destructor requires the deleter function of the unique_ptr which, by default, calls delete on the pointer to Foo_impl hence it requires the destructor of Foo_impl, and the destructor of Foo_impl is not declared in the incomplete type (the compiler doesn't know what it looks like). See Howard's answer on this as well.

Key here is that calling delete on an incomplete type results in undefined behaviour (§ 5.3.5/5) and hence is explicitly checked for in the implementation of unique_ptr.

Another alternative for this situation may be to use direct initialisation as follows;

std::unique_ptr<Foo_impl> m_impl { nullptr };

There seems to be some debate on the non-static data member initialiser (NSDMI) and whether this is a context that requires the member definition to exist, at least for clang (and possibly gcc), this seems to be such a context.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...