標籤:

C++的編譯單元要知道所有的實現?

遇到一個和前向聲明衝突的問題:

//VertexBuffer.h
class VertexBuffer{ };

//Mesh.h
class VertexBuffer;
class Mesh
{
public:~Mesh();
private: VertexBuffer* vertexbuffer_;
};

//Mesh.cpp
#include "VertexBuffer.h"
#include "Mesh.h"
Mesh::~Mesh() { delete vertexbuffer_; }

編譯Mesh.cpp沒問題。

問題來了,這個時候,我的第三個文件:

//MeshSceneNode.h
class Mesh;
class MeshSceneNode
{
public: ~MeshSceneNode();
private: Mesh* mesh_;
};

//MeshSceneNode.cpp
#include "Mesh.h"
#include "MeshSceneNode.h"
MeshSceneNode::~MeshSceneNode() { delete mesh_; }

編譯MeshSceneNode.cpp VS會警告我 deletion of pointer to incomplete type VertexBuffer ; no destructor called.

也就意味著編譯器在編譯MeshSceneNode.cpp,是需要知道所有的實現的,那我就得在MeshSceneNode.cpp中包含Mesh.h中使用了前向聲明但未包含定義的文件,這種做法實在有點噁心啊。

~~~~~~~~~~修改,將例子換成實例~~~~~~~~~~

感謝樓下各位的回答,例子換成實例,這樣更容易理解用的情景。對於Mesh.h/cpp這樣使用VertexBuffer我覺得挺合理的呀,因為不需要include VertexBuffer.h就能實現它的使用需求那就前向聲明咯。這也是推薦的做法嘛(可能我對前向聲明的理解還很膚淺)。

~~~~~~~~~~再次修改,貼上析構的代碼~~~~~~~~~~

貼了析構的代碼,並調整了代碼格式,看起來更舒服。另外我實際中指針成員存的是

Ref&

這樣的,類似shard_ptr。所以實際上析構裡面沒有delete,為了簡化問題,這裡改成raw pointer。

~~~~~~~~~~第三次修改,附上test project link~~~~~~~~~~

原因是陳碩聚聚說我粘貼的代碼依然能編譯通過。心裡不由一緊,畢竟所貼的代碼自己並未親自建立project,這次專門建立的project 並上傳到github。地址如下:

mi2think/TestProject · GitHub

讓我感到詫異的是,我發現了該工程中的如下代碼片段:

//TestMeshNodeMesh.h
#pragma once
#include "Common.h"
#include &

class SubMesh
{
public:
SubMesh(Mesh* mesh);
~SubMesh();

Mesh* GetMesh() const { return mesh_; }

void SetVisible(bool visible) { visible_ = visible; }
bool GetVisible() const { return visible_; }

const VertexBufferRef GetVertexBuffer() const { return vertexBuffer_; }
// 注釋掉下面行或者將這行實現放在.cpp文件,編譯可通過
void SetVertexBuffer(const VertexBufferRef vertexBuffer) { vertexBuffer_ = vertexBuffer; }
private:
// owning mesh
Mesh* mesh_;

bool visible_;

VertexBufferRef vertexBuffer_;
};

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

int GetSubMeshCount() { return submehs_.size(); }
private:
std::vector& submehs_;
};

正如注釋所述:將SetVertexBuffer的實現放在.cpp文件則可通過。

我個人對於什麼時候放.h,什麼時候放.cpp沒有一個準則。一般依據看起來舒服(第一考慮)或者方便。但這次居然掉進了這個坑。

目前我想到的是,因為這個SetVertexBuffer實現放在.h文件,導致編譯所有inlude Mesh.h的.cpp 在生成該成員函數的代碼時,會調用vertexBuffer_的析構函數,從而提示:deletion of pointer to incomplete type VertexBuffer ; no destructor called.

唉!

最後,終於弄明白了我遇到的問題。也就是@myd7349 和 @陳碩 聚聚回答中提到的。感謝你們!

《完結》


C++他爹:你需要什麼就include什麼,向前聲明是為了解決幾個類【在同一個文件里寫】而無法避免的一些問題。像你A和B根本就在不同的文件里,你使用向前聲明是錯誤的。


今天遇到了一個類似的問題。不過和題主的情形稍有差別(這是一個簡單的模型):Ongoing-Study/cpp/Pimpl/Pimpl_v1 at master · myd7349/Ongoing-Study · GitHub

嘗試將 main_window.h 中的:

//#define DEFINE_MAINWINDOW_DESTRUCTOR (0)

取消注釋,得到的編譯錯誤和題主的很像。然後我在 @陳碩 大大給的第一個鏈接里就找到了答案:

You
still need to write the visible class』 destructor yourself and define
it out of line in the implementation file, even if normally it』s the
same as what the compiler would generate. This is because although both unique_ptr and shared_ptr can be instantiated with an incomplete type, unique_ptr』s destructor requires a complete type in order to invoke delete (unlike shared_ptr which
captures more information when it』s constructed). By writing it
yourself in the implementation file, you force it to be defined in a
place where impl is already defined, and this successfully
prevents the compiler from trying to automatically generate the
destructor on demand in the caller』s code where impl is not defined.

這個帖子說的也是類似情況:

c++ - Deletion of pointer to incomplete type and smart pointers

此外,如果 Mesh.h 真是這樣寫的話:

class Mesh{ VertexBuffer* vertexbuffer_; };

題主可以考慮一下如下問題:

1. 為何沒見到 Mesh::~Mesh?如果題主忘記貼出來的話,看一下 Mesh::~Mesh 在刪除 vertexbuffer_ 的時候,VertexBuffer 的定義是否可見?如果不可見,VertexBuffer 的析構函數如何被調用?

2. 有指針成員的時候,良好的訪問控制是必要的。

PS:題主給出的代碼確實不能重現題主說的問題。

----


GotW #100: Compilation Firewalls (Difficulty: 6/10)

GotW #24: Compilation Firewalls

The Joy of Pimpls (or, More About the Compiler-Firewall Idiom)


你只要調用了類成員就會需要知道這個類的定義,包括寫明的和沒寫明的,沒寫明的包括默認構造函數和析構函數。你的情況應該是c.cpp裡面delete了A的指針,就需要析構函數,所以會報這個警告。如果只是把指針傳來傳去的話就不需要a.h了。


include寫在頭文件就能避免你不清楚你所依賴文件的依賴文件了


推薦閱讀:

switch語句中,case的後面為什麼必須是常量?
C/C++ 中怎樣優雅的寫多判斷 if 語句?
C語言的宏定義和C++的內聯函數有什麼意義?
C++ 類當中為什麼要有private?
怎麼用好《C++ Primer》(英文版)?

TAG:C |