閱讀931 返回首頁    go 阿裏雲 go 技術社區[雲棲]


C++:delete不完整類型的指針

簡單版

以下代碼編譯時會有warning:

class X;

void foo(X* x) {
    delete x;
}

在GCC4.1.2下,編譯出錯信息是:

warning: possible problem detected in invocation of delete operator:
warning: ‘x’ has incomplete type
warning: forward declaration of ‘struct X’
note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

這是因為在foo裏,編譯器看不到X的完整類型,沒辦法確定兩件事情:

  1. X有沒有自定義的析構函數(準確的說,有沒有non-trivial的析構函數)。
  2. X有沒有自定義的operator delete函數。

在不確定這兩件事情的情況下,編譯器隻能按最普通的方式去處理delete x

  1. 不調用任何析構函數。
  2. 調用全局的operator delete,通常來說就是直接釋放內存。

日常版

有一個我們平常會遇到的場景,就會觸發上麵這個問題。

以下是由三個文件組成的一個工程,其中用到了'pImpl'方法來隱藏實現,因此在接口類中放了一個std::auto_ptr,很簡單:

// test.h
#include <memory>

class A {
    class Impl;
public:
    A();
    void Func();
private:
    std::auto_ptr<Impl> mImpl;
};

// test.cpp
#include "test.h"
#include <iostream>

class A::Impl {
public:
    void Func() {
        std::cout << "Func" << std::endl;
    }
};

A::A(): mImpl(new Impl) {}

void A::Func() {
    mImpl->Func();
}

// main.cpp

#include "test.h"

int main() {
    A a;
    a.Func();
}

看起來很正常,但編譯時有warning:

$g++ test.cpp main.cpp
In destructor ‘std::auto_ptr<_Tp>::~auto_ptr() [with _Tp = A::Impl]’:
test.h:4:   instantiated from here
warning: possible problem detected in invocation of delete operator:
warning: invalid use of undefined type ‘struct A::Impl’
test.h:5: warning: forward declaration of ‘struct A::Impl’
note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined.

和前麵說的warning信息完全一致,看起來也是在調用delete時出的問題。但哪裏調用了delete呢?

答案是std::auto_ptr

上麵的代碼中,我們沒有給class A手動寫一個析構函數,因為編譯器自動生成的析構函數就是我們要的:析構時把mImpl析構掉。

那麼自動生成的析構函數長什麼樣子呢?大概是:

A::~A() {
    mImpl.~std::auto_ptr<Impl>();
}

展開了基本就是一句delete

A::~A() {
    delete mImpl._M_ptr;
}

這個析構函數的位置在哪呢?C++標準裏說會把自動生成的類成員函數放在類的定義中,那麼就是在test.h中。

問題清楚了:我們在編譯main.cpp時,看不到A::Impl的完整定義,但卻有一個自動生成的A::~A,其中delete了一個不完整的類對象!

解法

手動寫一個A的析構函數,位置要在能看到A::Impl完整定義的地方,也就是test.cpp:

```cpp
// test.h
#include <memory>

class A {
    class Impl;
public:
    A();
    ~A();
    void Func();
private:
    std::auto_ptr<Impl> mImpl;
};

// test.cpp
#include "test.h"
#include <iostream>

class A::Impl {
public:
    void Func() {
        std::cout << "Func" << std::endl;
    }
};

A::A(): mImpl(new Impl) {}
A::~A() {}

void A::Func() {
    mImpl->Func();
}

相關文獻

最後更新:2017-11-10 14:35:25

  上一篇:go  C++:為什麼unique_ptr的Deleter是模板類型參數,而shared_ptr的Deleter不是?
  下一篇:go  什麼是服務網格以及為什麼我們需要服務網格?