

unique_ptrでコンパイルエラーが出て、調べても分からないので質問させてください。

A.h

[code]

    #include <memory>
    class B;
    
    class A {
    
        public:
            static std::unique_ptr<A> create();
    
        private:
            void init();
            std::unique_ptr<B> _b;
    
    };
    
[/code]

A.cpp

[code]

    #include "A.h"
    #include "B.h"
    std::unique_ptr<A> A::create() {
        std::unique_ptr<A> ptr(new A);
        ptr->init();
        return ptr;
    }
    
    void A::init() {
        _b = B::create();
    }
    
[/code]

B.h

[code]

    #include <memory>
    
    class B {
        public:
        static std::unique_ptr<B> create();
    };
    
[/code]

B.cpp

[code]

    #include "B.h"
    
    std::unique_ptr<B> B::create() {
            std::unique_ptr<B> ptr(new B);
            return ptr;
    }
    
[/code]

main.cpp

[code]

    #include "A.h"
    
    int main(void) {
        auto a = A::create();
        return 0;
    }
    
[/code]

以下のようなエラーが出ます

[code]

        clang++ A.cpp B.cpp main.cpp -std=c++11
        In file included from main.cpp:1:
        In file included from ./A.h:1:
        /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2395:27: error: invalid application of 'sizeof' to an incomplete type 'B'
                                static_assert(sizeof(_Tp) > 0, "default_delete can not delete incomplete type");
                                                            ^~~~~~~~~~~
        /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2603:13: note: in instantiation of member function 'std::__1::default_delete<B>::operator()' requested here
                                __ptr_.second()(__tmp);
                                ^
        /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2571:46: note: in instantiation of member function 'std::__1::unique_ptr<B, std::__1::default_delete<B> >::reset' requested here
                _LIBCPP_INLINE_VISIBILITY ~unique_ptr() {reset();}
                                                                                                 ^
        ./A.h:4:7: note: in instantiation of member function 'std::__1::unique_ptr<B, std::__1::default_delete<B> >::~unique_ptr' requested here
        class A {
                    ^
        /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2603:13: note: in instantiation of member function 'std::__1::default_delete<A>::operator()' requested here
                                __ptr_.second()(__tmp);
                                ^
        /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2571:46: note: in instantiation of member function 'std::__1::unique_ptr<A, std::__1::default_delete<A> >::reset' requested here
                _LIBCPP_INLINE_VISIBILITY ~unique_ptr() {reset();}
                                                                                                 ^
        main.cpp:4:12: note: in instantiation of member function 'std::__1::unique_ptr<A, std::__1::default_delete<A> >::~unique_ptr' requested here
            auto a = A::create();
                             ^
        ./A.h:2:7: note: forward declaration of 'B'
        class B;
                    ^
        1 error generated.
    
[/code]

A.hでBの前方宣言ではなく、include B.hをすればエラーは出なくなりますが、  
こうするしかないのでしょうか？  
shared_ptrに変更した場合にはエラーは出ません。

このエラーで調べると、Pimpleに関する話題しか出なくて困っています。

よろしくお願いします。


CC BY-SA 3.0
2017-10-14T05:19:59.427
38689
2017-10-14T08:00:13.437
15334
question
c++ c++11
c++ 前方宣言でunique_ptrをメンバに持った場合のエラー(error: invalid application of 'sizeof' to an incomplete type 'B')


STLの`std::unique_ptr<T>`がテンプレートクラスとしてすべてインラインで定義されており、`delete
T`から最終的に`T`のデストラクターを呼び出します。そのため`T`（質問の例では`B`）が定義されなおかつデストラクターがアクセス可能であることが条件となります。

Pimplを使う方法もありますが、どうしてもということでしたら、どこかでインライン定義せず宣言と分離することです。影響範囲を狭めるのであれば`std::default_delete<B>::operator()`を分離することでしょうか。

A.hにて`std::unique_ptr<B>`よりも手前の位置で

[code]

    namespace std {
        template<> struct default_delete<B> {
            constexpr default_delete() noexcept = default;
            void operator()(B* ptr) const noexcept;
        };
    }
    
[/code]

と特殊化しておき、B.cppで

[code]

    void std::default_delete<B>::operator()(B * ptr) const noexcept { delete ptr; }
    
[/code]

と定義すればクラス`B`との依存関係を分離できます。


CC BY-SA 3.0
2017-10-14T06:22:18.250
38690
2017-10-14T06:22:18.250
4236
38689
answer


[`unique_ptr`の説明](https://cpprefjp.github.io/reference/memory/unique_ptr.html)を見る限り、テンプレートの第2引数D(のデフォルト)である`default_delete<T>`クラスでは`T`が完全型であることを要求しているように見えます。

[`default_delete<T>`の説明](https://cpprefjp.github.io/reference/memory/default_delete.html)より引用

> 型Tが不完全型である場合、operator()の実行は不適格となる。

ですので、`unique_ptr`のテンプレート`T`を前方宣言できないのは仕様と思います。


CC BY-SA 3.0
2017-10-14T07:07:57.070
38691
2017-10-14T07:07:57.070
20098
38689
answer


[Pimpleに関する話題で出てくる回答](https://qiita.com/false-
git@github/items/79bf1b6acc00dc43d173#comment-9071ab88afe79f2cf834)の通り、Aのデストラクタを非インライン化すれば[コンパイルできます](https://wandbox.org/permlink/AltLgDb9coJiVD3a)。

A.h

[code]

    #include <memory>
    class B;
    
    class A {
    
        public:
            static std::unique_ptr<A> create();
            ~A(); // デストラクタを非インライン化
    
        private:
            void init();
            std::unique_ptr<B> _b;
    
    };
    
[/code]

A.cpp

[code]

    #include "A.h"
    #include "B.h"
    std::unique_ptr<A> A::create() {
        std::unique_ptr<A> ptr(new A);
        ptr->init();
        return ptr;
    }
    
    A::~A() = default; // デストラクタを非インライン化
    
    void A::init() {
        _b = B::create();
    }
    
[/code]

参考：

  * [unique_ptr はクラスの正確なサイズを知らないといけない? - Qiita](https://qiita.com/false-git@github/items/79bf1b6acc00dc43d173)


CC BY-SA 3.0
2017-10-14T08:00:13.437
38693
2017-10-14T08:00:13.437
7291
38689
answer