標籤:

C++什麼情況下,需要重載一個成員函數的const和非const版本?

#include&
class A {
public:
void func1() const{ //不改變對象
//.....
std::cout &<&< "f1 const" &<&< std::endl; } void func2() { //改變對象 //... std::cout &<&< "f2 not const" &<&< std::endl; } }; int main() { A a; a.func1(); const A b; b.func1(); A c; c.func2(); const A d; //d.func2(); 提示錯誤 return 0; }

一個成員函數有兩種狀態 ,不改變對象(func1)和改變對象(func2)。

1.不改變對象的成員函數寫成const成員函數,可變對象和const對象都可以調用。沒問題

2.改變對象的成員函數寫成非const成員函數,可變對象可以調用,const成員從邏輯上就不應該調用這個函數。調用也會出錯。

問題一:

那麼在什麼情況下需要對const和非const版本成員函數進行重載呢? 實在想不出來。

像下面這樣: 如果有這樣的情況,什麼情況? 為什麼?

class A {
public:
void func3() const {
//...
}
void func3() {
static_cast&(*this).func3(); //1
static_cast&(*this).func3();//2
}
};

問題二: 假設確實有情況需要重載,那麼上面的 1和2有什麼區別,哪個是對的?

想了半天,把自己繞暈了,望前輩解答,謝謝。


譬如說

template&
struct Array
{
SelectReturnType& operator[](int i)const;
WritableProxy& operator[](int i);
};


謝邀。STL里就有一個很經典的場景:

vector& vec = {1, 2, 3};
const vector& c_vec = {4, 5, 6};

vector&::iterator iter = vec.begin();
*iter = 10; // OK

vector&::const_iterator c_iter = c_vec.begin();
*c_iter = 40; // Error: modifying a const

這裡vector::begin()會根據這個vector本身是不是const來調用不同的重載。為什麼要這麼做呢?因為雖然begin()本身並不直接改變對象,但它想要把const屬性『轉發』給返回值類型——如果不進行這種重載,那麼:

1. 如果只實現了非const版,那const vector(在C++98里)就沒辦法遍曆元素了;

2. 如果只實現了const版,那非const的vector就沒辦法通過它返回的迭代器來修改元素了。

當然,這裡的細節容易把人搞暈,這也就是為什麼C++11里出現了cbegin()這樣的東西來明確語義——如果你明確知道後面不會通過迭代器來修改對象,那麼即便你面對的是一個非const的vector,你也應該總是使用cbegin()而不是begin(),以保證萬一後面你犯糊塗的時候能得到來自『編譯錯誤』的保護。


通常是返回值類型不同。

常量對象(使用受常量限制),成員函數func返回一個功能弱一點(或者限制多一點)的對象類型A。

非常量對象(使用不受常量限制),成員函數func返回一個功能強一點(或者限制少一點)的對象類型B。

非成員函數,也可以這樣做(利用函數參數類型來重載),總是為了性能的提升。

也可能是和返回值無關。

單線程,快速回收,簡潔,指令級速率優化的shared_ptr(自己的C++小工具系列3) - 知乎專欄

上文我提到了,如果想要快速回收一個已經」失效「(lock不出來一個有用的shared_ptr)了的weak_ptr,不僅需要避免在其拷貝時產生弱引用計數增加,還需要儘可能的在對其進行訪問時趕緊減少弱引用計數,讓自己真正的失效,斬斷對引用計數器的引用。由於減少弱引用計數有可能觸發引用計數器的回收從而需要標記它」已回收「,減少弱引用計數這個操作始終免不了可能的非const操作。所以不得不把大部分對weak_ptr的訪問函數都提供了非const版本(注意和const版本相比,時間開銷是沒有增加的,條件語句一個都沒有多,只是把各種回收操作提前了而已。回收完成了之後,訪問開銷就和真正的空指針無異了),當然這也是因為俺不喜歡用mutable關鍵字的緣故。

(有的同學可能會質疑,只需要避免弱引用計數增加就可以了,靜靜的等最終的析構就可以了。。那我想說,shared_ptr的deleter等設計,本就是為了讓持有shared_ptr的關聯對象早點析構,不應該製造一個長時間無法釋放(很可能等到程序結束才釋放)的對象(儘管是一個小對象))

----------------------------------------------------------------------------------------------------------------------------------

換(總)而言之,提供一個重載,有時候可以讓你在非const 版本裡面放開手腳,做一些在const版本里不敢做的優化,何樂而不為呢。


並不是一定要有兩個版本。

比如一個對象a。

const a只能使用帶const的成員函數。

a則可以隱式轉化成const a,所以帶不帶const的成員函數都能使用。

如果一個成員函數一定不改變類內部的成員,那就加上const。這也可以在你不小心寫了改變內部成員的代碼時,編譯器給你提示報錯。

如果一定會改變內部成員(準確說是這個函數內部有改變成員的代碼),那就只寫不帶const的版本。

const的a無法隱式轉化成非const的a,所以不能使用這個函數。這也是為了保證const a自身的意義---這個a不會發生改變。

而如果代碼內部沒有改變狀態的代碼,但他會返回一個引用或者指針b,而改變b也會導致a的改變的話,就要寫const和不帶const的版本。

const版本返回常引用,這也是不能被改變的。

誠然如你所寫,用static_cast強制轉換可以去掉const(其實應該用const_cast),所以這個約束其實是「虛」的。

這種約束只是在編譯時起作用,並不能阻止你運行時指針亂指,強行更改。

換句話說,加不加const不是寫完代碼後,看這個函數的代碼有沒有改變這個對象。

而是你在設計的時候決定,這個函數該不該改變這個對象。


它不是為了專門給你non-const對象和const對象用的。重載const和non-const,最經常用的地方就是獲取成員信息部分,因為你獲取成員有可能只是為了讀取,有可能是為了修改。例如operator[]的重載 大部分都有兩個版本,比如std::vector,你可以用auto n = v[0] 這裡調用operator[] 只是為了讀取,所以可能是調用const版本。當你要修改的時候 v[0] = 1 肯定要用non-const 版本。

當然這個例子看不出什麼區別,指效率方面,但當你用了一些,比如ProxyClass之類的,讀和取的效率已經有明顯差別了,那就體現出作用了。

另外問題二,你那樣的寫法是沒有什麼意義的。。完全體現不出作用,如果你的函數非得是這樣的,就不用重載。


首先最根本的一件事是你用const對象是調用不了非const的成員函數的。如果你希望這個成員函數可以用const對象調用,那它必須是const的。

struct A
{
void foo()
{

}

void goo() const
{
// x = 1; // 不可以
}

void goo()
{
x = 1; // 可以
}

int x;
};

void bar(const A a)
{
//a.foo(); // 不可以
a.goo(); // 可以
}


首先回答const的部分. 其實如果程序員夠細心, 完全沒必要這麼修飾, 你保證函數內不改變成員就可以了. 然而人是會犯錯的, 於是就有了這個關鍵字讓編譯器報錯. 比如說你在const成員函數裡面調用了一個第三方庫的函數, 如果它瞎動你成員數據, 都不用看文檔, 編譯器直接報錯, 爽不爽. 其實就是為了早發現bug, 讓編譯器分擔debugger的工作.

然後回答重載的部分. 首先重載只要signature不一樣, 原則上就是可行的. const本身算signature, 所以編譯器能區分開來沒意見. 至於究竟有什麼用嘛. 主要就是配合STL的那些容器, operator[]. 還有樓上說的返回類型不同. 感覺是屬於語法糖範疇的, 對可讀性貢獻比較大.


推薦閱讀:

為什麼 C++ 有指針了還要引用?
C語言如何執行buf中的代碼?
怎樣才是一個基本水平的c++程序員?
C++11中用{}初始化,有等於號和沒等於號的區別是什麼?

TAG:C | CC |