2013-02-11 31 views
7

對於容器類(如std::vector),存在兩個不同的恆定性概念:容器的大小(即大小)和元素的大小。看來,std::vector混淆這兩個,使得下面簡單的代碼不會編譯:const與容器的非常量及其內容

struct A { 
    A(size_t n) : X(n) {} 
    int&x(int i) const { return X[i]; } // error: X[i] is non-const. 
private: 
    std::vector<int> X; 
}; 

注意的是,即使數據成員(中三分,將開始數據和結束的&結束的分配緩衝區)的std::vector不是通過調用它的operator[]改變,這個成員不是const - 這不是一個奇怪的設計?

還要注意,對於原始指針,恆定的煩躁這兩個概念都整齊分離,使得所述相應的原始指針代碼

struct B { 
    B(size_t n) : X(new int[n]) {} 
    ~B() { delete[] X; } 
    void resize(size_t n);     // non-const 
    int&x(int i) const { return X[i]; } // fine 
private: 
    int*X; 
}; 

作品就好了。

那麼當使用std::vector(不使用mutable)時,正確/推薦的方法是什麼?

是一個const_cast<>

int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; } 

認爲是可接受的(X已知是非const,所以沒有UB這裏)?

EDIT只是以防止進一步的混亂:我想修改的元件,即所述容器的內容而非容器本身(大小和/或存儲器位置)。

+0

'std :: vector'的數據可以通過調用'operator []'來改變。正如你寫的A :: X,a.x(1)++;是完全合法的,並且修改了向量的內容。 – 2013-02-11 16:14:10

+1

@DavidSchwartz矢量的*內容*不是它的實際*數據*(儘管你可能在邏輯上將它們關聯起來)。如果你檢查'std :: vector',它只有3個指針作爲數據(數據的開始和結束以及緩衝區的結束)。這些保持不變。 – Walter 2013-02-12 19:52:10

回答

13

C++僅支持const的一個級別。至於編譯器 而言,它是按位const的:以「位」,實際上在 對象(即sizeof計)不能沒有 玩遊戲(const_cast等)進行修改,但別的是公平 遊戲。在C++的早期(20世紀80年代後期,90年代初), 就位設計的優點而言有很多討論,其中常量與邏輯常量(也稱爲Humpty-Dumpty常量, ,因爲Andy Koenig曾告訴我,當程序員使用 const時,它意味着程序員希望它意味着什麼)。 這個共識終於合併了有利於邏輯常量。

這意味着容器類的作者必須使 成爲一種選擇。 容器的容器部分的元素,還是不是。如果它們是容器的一部分,那麼如果容器是const,則它們不能被修改。有沒有辦法 提供一個選擇;容器的作者必須選擇其中一個或另一個爲 。在這裏,似乎也有一個共識: 元素是容器的一部分,如果容器是const,則它們不能被修改。 (也許與 C風格的陣列在這裏起到了作用;如果C風格的陣列是const,則 那麼你不能修改它的任何元素。 修改矢量的大小(可能是爲了保護 迭代器),但不是其元素。沒有真正令人滿意的解決方案;我能想到的最好的辦法是創建一個新類型 一個新類型,其中包含一個mutable std::vector,並提供 轉發函數,這對應於const 的含義,我需要在這個特定情況下。如果要區分 三個級別(完全常量,部分常量和非常量),則需要導出 。基類僅公開完全常量和部分常量函數(例如,const int operator[](size_t index) const;int operator[]( size_t index);,但不是void push_back(int););允許插入和移除元素的功能 僅在派生類中公開。不應該插入或移除元素的客戶端僅向基類傳遞非const引用 。

+0

+1不錯討論。然而,我不相信容器類的設計者沒有選擇的事實。我們可以實現一個容器類,它將元素的常量作爲模板參數,並允許在const和非const容器之間進行移動轉換(使用智能指針)。不過,您的基礎派生設計看起來更簡單。 – Walter 2013-02-12 09:30:49

+0

@Walter容器類的設計者有各種各樣的選擇。在這種情況下,std :: vector的設計者似乎有一個普遍的共識 - 我知道大部分的標準容器都是一樣的。在全球範圍內,除了特殊情況(例如'std :: array')外,對於提供'const'(無,部分或完整)三個級別的容器似乎並沒有太大需求。 (在標準日之前,我的一個數組類以大小作爲構造函數參數,並且沒有可能在稍後改變它。) – 2013-02-12 09:53:36

+0

@JamesKanze:解釋非常好,但是頂部的前兩句開頭句子給了按位const是C++支持的那種印象。 *(我希望沒有太多的C++程序員是TL; DR)*這個開頭語句在編譯器後端是正確的,但是前端只考慮'const'是一個語法輔助 - 它是用於類型檢查,就是這樣。 (順便說一句,C++ 11引入了編譯時'constexpr'。)邏輯常量只存在於程序員的大腦中。當開始多線程編程時,C++程序員會意識到這些差異。 – rwong 2014-09-22 17:44:34

3

這不是一個奇怪的設計,這是一個非常慎重的選擇,正確的一個恕我直言。

B例子不是一個std::vector一個很好的比喻,一個更好的比喻是:

struct C { 
    int& get(int i) const { return X[i]; } 
    int X[N]; 
}; 

但該陣列可以調整大小非常有用的差異。上述代碼與原始數據無效,數組(或vector)元素在概念上是包含類型的「成員」(技術上說是子對象),所以您不應該通過const成員修改它們功能。

我會說const_cast是不可接受的,並且都不使用mutable除非作爲最後的手段。你應該問爲什麼你想改變一個const對象的數據,並考慮使成員函數非const。

+0

這是一個解釋的問題。如果你將'vector'視爲一個可重定義的C數組,那麼我同意。然而,'resize()'等函數在哪裏適合這個比喻呢?它們必須不僅僅是常量:您希望能夠對元素進行非const訪問,但不能達到大小。這是不可能的'std :: vector'。我認爲這種解決方法通常是提供迭代器,它通常允許更改內容,但不允許容器。 – Walter 2013-02-11 16:27:55

+0

這只是一個比喻,不要太直接地說,無論如何'resize'不是const的,所以它可以改變對象。不確定你對迭代器的含義,但沒有一個標準容器會給你一個const const iterator的非const迭代器。 – 2013-02-11 20:17:02

+0

我對迭代器的含義是,如果你有一個(非const)迭代器,你不能修改容器。所以容器保持常量,而元素可以被修改。 – Walter 2013-02-12 09:32:22

4

不幸的是,不像指針,你不能這樣做

std::vector<int> i; 
std::vector<const int>& ref = i; 

這就是爲什麼std::vector不能這兩種const間的歧義,因爲它們可能適用,並且它必須是保守的。我個人而言,會選擇做這樣的事情

const_cast<int&>(X[i]); 

編輯:正如一位網民準確地指出的那樣,迭代器模型這種二分法。如果您將vector<int>::iterator存儲到開頭,則可以使用const方法取消引用它,並取回非const int&。我認爲。但是你必須小心失效。

+1

迭代器的使用提出了一個有趣的解決方案:一個_view_類,它只包含開始和結束迭代器,並且只提供他想要的有限功能。 (就此而言,帶有指向矢量的視圖類也可以。) – 2013-02-11 16:45:50

-1

我會建議使用std::vector::at()方法而不是const_cast

+0

vector :: at()的const重載將返回一個const參考,這樣不會幫助 – 2013-02-11 21:26:07