unique_ptrがImplモードを実現する際に発生する問題分析

8052 ワード

背景
クラスの設計と定義では、「実装へのポインタ」またはPImplと呼ばれることに慣れています.たとえば、次のクラスがあります.
// widget.h(  )
class widget {
    //     
private:
    struct impl;  //         
    impl* ptr;
};
// widget.cpp(  )
struct widget::impl {
   //     
};

1、impl類の変更については、widget類の対応する編成ユニットの再編成に影響を及ぼさず、編成の観点から編成効率を大きく提供することができる.2、安定したABIインタフェースの構築に有利である.すなわち,あるライブラリがimplを使用すると,implの実装が更新された後,新しいライブラリは古いABIと互換性がある.
に質問
ノーマルコード
次のスマートポインタコードと比較するために、raw pointerを通常使用するコードの例を示します.
//widget.h
struct Impl;
class Widget{
public:
    ~Widget() = default;
private:
//    std::unique_ptr ptr_;
    Impl* ptr_;
};

//widget.cpp
#include "widget.h"
#include 
class Impl {
};
#include "widget.h"

int main(int argc, char** argv) {
    Widget widget;
    return 0;
}
TARGET=main
all:$(TARGET)
OBJ=widget.o main.o
main:$(OBJ)
    g++ -std=c++11 -g -o $@ $(OBJ) -lpthread 

%.o:%.cpp
    g++ -std=c++11 -g -c $<
    
clean:
    rm -f *.o
    rm -f $(TARGET)

スマートポインタ置換コード
c++11ではsmart pointerがあり、raw pointerをスマートポインタで置き換えるのが普通です.例えばwidget.cppは次のコードに変更されました.
//widget.h
struct Impl;
class Widget{
public:
    ~Widget() = default;
private:
	std::unique_ptr<Impl> ptr_;
    //Impl* ptr_;
};


makeを実行した後、私たちは次のようにエラーを報告したことに気づきました.
In file included from/usr/include/c++/4.8/memory:81:0, from test.h:4, from test.cpp:1:/usr/include/c++/4.8/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_tp>::operator()(_Tp*) const [with _Tp = Widget::Impl]’:/usr/include/c++/4.8/bits/unique_ptr.h:184:16: required from ‘std::unique_ptr<_tp _dp="">::~unique_ptr() [with _Tp = Widget::Impl; _Dp = std::default_deleteWidget::Impl]’ test.h:6:7: required from here/usr/include/c++/4.8/bits/unique_ptr.h:65:22: error: invalid application of ‘sizeof’ to incomplete type ‘Widget::Impl’ static_assert(sizeof(_Tp)>0, ^ make: *** [test.o] Error 1
最初は霧がかかっていて、はっきりしなかった.私たちは間違ったところに注目します.unique_ptrここでは,析出時に対応するImplクラスの析出関数を呼び出す必要があるためである.ここでは前置き声明を使っていますstatic_assert(sizeof(_Tp)>0は、ここでImplクラスの完全な定義を知る必要があることを示す.では、問題ははっきりしています.Widgetクラスオブジェクトは、役割ドメインが終了すると自動的に構造関数が生成されますが、構造関数では、その構造関数を呼び出すには、Implクラスの完全な定義を知る必要があります.
ソリューション
Widgetに対して構造関数を宣言し、定義された現在のアセンブリユニットで、Implの定義を表示させることができます.私たちはまずwidgetにいます.hで構造関数を宣言しwidget.cppでは構造関数を定義しwidget.cppではクラスImplの定義が見られます.コードは次のとおりです.
//widget.h
struct Impl;
class Widget{
public:
    ~Widget();
private:
//    std::unique_ptr ptr_;
    Impl* ptr_;
};

//widget.cpp
#include "widget.h"
#include 
class Impl {
};

Widget::~Widget() = default;

まとめ
何事も問題に直面して、根掘り葉掘り聞く精神があってこそ、一歩一歩前進することができる.一部の問題はそれを知らなければならないので、理解することができます.道は果てしなく広がっていて、私は上下して求めます.