1. 程式人生 > >C++:關於重複定義的解決思路

C++:關於重複定義的解決思路

一、VC編譯原理

解決重定義問題,首先要明白VC的編譯原理:

  • VC只編譯cpp檔案,這些cpp檔案構成將來的exe;
  • 當VC編譯A.cpp檔案的時候,如果遇到了語句#include "B.h",實質上是將"B.h"檔案中的程式碼全部“複製”到A.cpp中,然後再繼續編譯A.cpp。
  • 當在B.h檔案中定義全域性變數a等,即使使用了避免檔案重複包含的方法(如下節提到的兩種方法),是不能避免“A.cpp中#include"B.h",C.pp中#include"B.h",然後提示變數a重複定義”的問題,只能保證“A.cpp中多次出現#include"B.h”而不會提示變數a重複定義”。
  • 全域性變數、函式、結構體一定要在.cpp檔案中定義,在.h檔案中宣告,一定不要在h檔案中定義,否則會出現重複定義的問題。

二、標頭檔案避免類重複包含的方法

1. 微軟預編譯控制

#if _MSC_VER > 1000
#pragma once
#endif
... ... // .h檔案正文

在.h檔案最開始的地方加上這段程式碼,即可避免標頭檔案重複include。但是需要注意的是#pragma once 這條語句只有VC編譯器大於1000才可以支援(反正VC++6.0及以上是可以的),_MSC_VER就是Microsoft的C編譯器的版本。
#pragma once則由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。帶來的好處是,你不必再費勁想個巨集名了,當然也就不會出現巨集名碰撞引發的奇怪問題。對應的缺點就是如果某個標頭檔案有多份拷貝,本方法不能保證他們不被重複包含。當然,相比巨集名碰撞引發的“找不到宣告”的問題,重複包含更容易被發現並修正。

2、巨集定義方式

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__ 
... ... // .h檔案正文
#endif

#ifndef的方式依賴於巨集名字不能衝突,這不光可以保證同一個檔案不會被包含多次,也能保證內容完全相同的兩個檔案不會被不小心同時包含。當然,缺點就是如果不同標頭檔案的巨集名不小心“撞車”,可能就會導致標頭檔案明明存在,編譯器卻硬說找不到宣告的狀況 。不過如果是編譯器自動生成的類一般都會自動根據編譯次數(唯一)來自動生成唯一的巨集名。

三、出現重複定義的變數的解決方法

在專案同時使用第三方庫GuiLib和CJ609Lib,編譯提示結構體CMenuItemInfo

重定義,後來研究了一下,發現Guilib和CJ609Lib中都全域性定義了同一個結構體名CMenuItemInfo,而解決方法有兩種:使用巨集定義規避和使用名稱空間

1.巨集定義規避

這種方法很簡單,就是類似C++利用巨集避免標頭檔案重複的形式,直接給例項,在兩個關於CMenuItemInfo結構體定義的位置加上一下的巨集定義就行:

#ifndef __CMenuItemInfo_LOCAL_DEFINED//避免結構體CMenuItemInfo重複定義
#define __CMenuItemInfo_LOCAL_DEFINED
struct CMenuItemInfo : public MENUITEMINFO_LOCAL {
    CMenuItemInfo()
    {
        memset(this, 0, sizeof(MENUITEMINFO_LOCAL));
        cbSize = sizeof(MENUITEMINFO_LOCAL);
    }
};
#endif//__CMenuItemInfo_LOCAL_DEFINED

這樣做的好處是,用CMenuItemInfo定義具體物件時,直接使用CMenuItemInfo,程式會自動使用編譯過程遇到的第一個CMenuItemInfo的定義。
但這種方法有一個前提:兩個結構體的定義必須一致,也就是說上述關於CMenuItemInfo的兩個定義必須一致,很巧的是Guilib和CJ609Lib關於CMenuItemInfo的定義確實一致。

2.使用名稱空間

這種方法更加廣泛,尤其適合於重名且定義的內容不一樣的情況。這種方法等有時間了,我再在下邊補充,本次使用了巨集定義就解決了上述問題。
PS:最後補充一點,有的時候提示@.obj已經在@.obj中定義的時候,在專案屬性配置中啟用預編譯頭可能會解決這個問題,但不知道是什麼原理。