2014-01-13 93 views
1
class Test 
{ 
public: 
    Test() : i(0), ptr(&i) {} 
    int i; 
    int *ptr; 
    void change_const (int x) const { *ptr=x; } 
}; 

int main() 
{ 
    const Test obj; 
    obj.ptr = &obj.i; // error 
    obj.change_const(99); 
    return 0; 
} 

雖然objptrint *const類型,構造可以使他指向const int類型的i。明確的嘗試做到這一點當然失敗了。爲什麼構造函數提供有關const正確性的漏洞?其他非直接明顯的漏洞如const正確性與const對象和成員指針,構造函數漏洞

int *ptr; 
const int **c_ptr = &ptr; // error 
const int c = 10; 
*c_ptr = &c; 
*ptr = 20; // because here change of c possible 

也被認爲可以防止。

+0

C++有時會給你足夠的繩子來吊死你自己。只是不要首先編寫小狗代碼,並遵守規則 –

回答

2

很明顯,構造函數(或者至少初始化列表,如果不是ctor的主體)需要能夠寫入一個值到i

碰巧,C++實現這個的方式是在構造函數(和析構函數)中使this成爲指向非const的指針。基本上,const-obj直到構造函數完成執行纔開始。這就是存在漏洞的原因,因爲如何構建const限定對象的技術問題是一個簡單但不完美的解決方案。

也許它原則上可以做不同的事情。我想你需要一個單獨的const版本的構造函數,其中編譯器應用不同的規則(就像正常的成員函數可以是const一樣),將數據成員當作const,因此(1)允許它們被初始化但未被分配, (2)禁止從&i開始ptr的初始化,因爲後者的類型爲int const*。 C++不會這樣做,因此它有這個漏洞,你已經開始了。如果確實如此,人們在某些情況下編寫構造函數會遇到更多困難,所以這是一個設計權衡。

請注意,在其自己的構造函數或析構函數中,類似地,volatile限定的對象不是volatile

+0

是的,我也想過構造函數的一個單獨的'const'版本的這些可能性,但從理論上講,最好只有類似你的觀點(2):改變運算符'&'的含義以返回一個具有'type'的(成員)變量的'const * type'。關於第(1)點,'this'實際上應該保持非const,直到構造函數完成以允許像jogojapan(循環,...)這樣的複雜賦值。因此,額外的'const'構造函數中'&'的另一個含義可以阻止這個漏洞?也許? – mb84

+0

@ mb84:嗯,你必須確定'&'的含義是必要和充分的。如果你想允許'i = 1',那麼人們會認爲'init(&i)'用'void init(int * p){* p = 1; }'。在這種情況下,我想你會強迫他們使用'const_cast',但它變得很難看。這不僅僅是指針,同樣的問題也適用於引用。假設你在'&i'類型爲'const int *'的同時,禁止將'i'綁定到非const引用(即使它是非const的左值)(即使'i'是int ')。這不是很好,這並不是說它不起作用。 –

+0

是的,它似乎變得醜陋... – mb84

4

const是一個語言級別的概念。也就是說,當您編譯代碼並將其作爲機器代碼執行時,所有數據都會被視爲數據或多或少。請注意,我會說「或多或少」,因爲我們忽略了這樣一個事實:理論上,const數據可能存儲在只讀頁面中,並在寫入時觸發頁面錯誤;但由於頁面大小的粒度不同,這種情況並不常見。因此,發生了以下情況:

您的構造函數將ptr的值初始化爲指向i的地址。由於您的obj對象是const,因此無法直接修改i的值,此外,您無法更改ptr指向的位置。但是,您可以訪問和操縱ptr指向的內存(在這種情況下,值爲i)。

因此,由於編譯器不檢查/知道/關心ptr指向i,因此它沒有發現違反const的情況。相反,它只是看到你修改ptr指向的數據。

+0

因此,編譯器不會在構造函數中不尊重/關心'this'的類型嗎? 'const Test * const this; this-> ptr =&(this-> i);' – mb84

+0

@ mb84:是的,請參閱下面的Steve Jessop的回答。直到對象被初始化之後,對象的單個構造函數完成執行後才能保證'const'。 – RageD

1

史蒂夫·傑索普回答了這個問題,但對於它的價值,這裏是從標準(重點煤礦)的報價:

12.1/4構造函數不得虛擬(10.3)或靜態(9.4) 。可以爲const,volatile或const volatile對象調用構造函數。構造函數不應被聲明爲const,volatile或const volatile(9.3.2)。 const和volatile語義(7.1.6.1)不適用於正在構建的對象。它們在最大派生對象(1.8)的構造函數結束時生效。構造函數不應使用ref-qualifier進行聲明。

因此*this從構造函數的角度來看不是一個常量對象,即使創建了一個常量對象。這可能有不同的設計,但是然後常量對象的構造函數比非常量對象的構造函數更不靈活;例如,他們總是必須初始化初始化器列表中的所有成員;他們不能在構造函數的主體中使用循環等來爲複雜成員設置值。

+0

感謝您的回答,另請參閱Steve Jessop的評論。你怎麼看? – mb84

+0

我認爲有一個const的版本的構造函數是有道理的,並且只允許用來初始化const對象。現在使用統一的初始化語法和移動語義,複雜成員通常可以在沒有構造函數體的情況下被初始化。無論如何,Const對象是很少見的。所以我認爲如果我要重新設計C++,我會提出這個建議。但現在改變它顯然會導致很多向後兼容性問題。 – jogojapan