今天打算用C++模擬一下Java的Object對象。需求很簡單,通過一個自定義用戶類型包裝一個內建類型,並提供equals、hashCode、=和== 4種函數。
源碼如下:
#pragma once #ifndef ENTITY_H_ #define ENTITY_H_ #include <functional> template<typename T> class Entity { private: T obj; Entity(const Entity&); public: Entity(T t) :obj(t) {}; bool equals(Entity& other) { std::hash<T> tohash; return tohash(obj) == other.hashCode(); } size_t hashCode() { std::hash<T> tohash; return tohash(obj); } Entity<T>& operator=(const Entity& rv) { obj = rv.obj; return *this; } friend bool operator==(const Entity& left, const Entity& right) { return left.hashCode() == right.hashCode(); } }; #endif // !ENTITY_H_
C++11 提供了Hash方法正好拿來使用,可是在測試==的時候卻發現編譯器報錯:“size_t Entity<int>::hashCode(void)”: 不能將“this”指針從“const Entity<int>”轉換為“Entity<int> &”。說實話,平時Java用習慣了,猛然看見C++的報錯信息真是一臉懵逼。后來在網上查了一些資料逐漸明白了個中緣由。
首先修改代碼:
size_t hashCode() const { std::hash<T> tohash; return tohash(obj); }
測試通過!做法很簡單,但是要說明清楚這個問題,我們還得從對象方法與函數的區別談起。
1. 函數與方法
函數是一個很原始的概念,在古代面向對象的語言還沒有誕生。大家為了封裝一些公共算法發明了函數這個概念,通常是根據實參經過函數體返回一個計算結果,並且實參要和形參對應。后來,面向對象的語言出現了,大家開始考慮讓對象實施某些行為,這就是——方法。本質上,函數叫function和方法叫method。對象方法之於普通函數的最大區別是,它可以直接訪問對象內部的成員屬性(field)。在Java中我們通過this.field的方式就能夠訪問,在C++中更普遍的做法是this->field。這里就很奇怪了,為什么在method內部能直接使用this呢?原因是語言在編譯過程中,編譯器將對象(this)作為參數置入了method中。因此實際上,method的真實形象應該是這樣的:method(this, args)。熟悉面向過程語言特性的朋友一定清楚,如果不用面向對象的思想實際上也可以用function模擬method,即也是將對象本身作為參數傳遞給function。
2. function(args) const
const blob b(2);
這里,b是類型blob的一個const對象。它的構造函數被調用,並且參數為“2”。由於編譯器強調對象為const,因此它必須保證對象的數據成員(fields)在其生命周期內不被改變。然而,僅僅是在類聲明中給出const還不能保證成員函數按聲明的方式去做。所以,編譯器會強制程序員在定義函數時重申const。這就是function(args) const的由來。
3. 結論
在明白了以上兩點以后,我們再回到本例中。由於operator==(const Left& lv, const Right& rv)在函數中要求提供的兩個參數對象都必須是const類型。因此在整個函數體中,這些對象執行的方法也必須是const類型。然而現在我們知道成員函數會隱式的調用對象本身,換句話說hashCode()方法會分別調用Left與Right,而這個方法並沒有被重申為const。這就出現了開頭看到的那段異常:“size_t Entity<int>::hashCode(void)”: 不能將“this”指針從“const Entity<int>”轉換為“Entity<int> &”!
