[英]Constant class members, assignment operator and QList
請確認我是否正確並告訴我是否有更好的解決方案:
我理解具有常量成員的對象,如int const width;
無法由編譯器隱式創建的合成賦值運算符處理。 但是QList(我想std :: list)也需要一個工作賦值運算符。 因此,當我想使用具有常量成員和QList的對象時,我有三種可能性:
那是對的嗎? 還有其他優雅的解決方案?
我也想知道我是否可以:
編輯:我自己從不分配這個類的對象。 它們僅由復制構造函數或重載構造函數創建。 因此,只有容器才需要賦值操作符而不是我自己。
EDIT2:這是我創建的賦值運算符。 我不確定它是否正確。 Cell有兩個參數構造函數。 這些參數使用初始化列表設置兩個常量成員。 但該對象還包含其他變量(非常量)成員。
Cell& Cell::operator=(Cell const& other)
{
if (this != &other) {
Cell* newCell = new Cell(other.column(), other.row());
return *newCell;
}
return *this;
}
EDIT3:我發現這個帖子幾乎有同樣的問題: C ++:STL麻煩與const類成員所有答案結合在一起回答了我的問題。
您可能是C ++的新手,並希望它的行為類似於Python,Java或C#。
將不可變Java對象放入集合中是很常見的。 這是有效的,因為在Java中,您並不真正將Java 對象放入集合中,而只是將Java 引用放在Java對象中。 更准確地說,集合內部由Java引用變量組成,並且分配給這些Java引用變量根本不會影響引用的Java對象。 他們甚至沒有注意到。
我故意說“Java對象”,“Java引用”和“Java變量”,因為術語“對象”,“引用”和“變量”在C ++中有完全不同的含義。 如果你想要可變的T
變量,你需要可變的T
對象,因為變量和對象在C ++中基本相同:
變量由對象的聲明引入。 變量的名稱表示對象。
在C ++中,變量不包含對象 - 它們是對象 。 分配給變量意味着更改對象(通過調用成員函數operator=
)。 沒有其他辦法了。 如果你有一個不可變對象,那么在沒有明確地破壞類型系統的情況下,賦值a = b
就不可能工作,如果你這樣做,那么你已經有效地向你的客戶說謊了對象是不可變的。 做出承諾然后故意破壞它是沒有意義的,不是嗎?
當然,您可以簡單地模擬Java方式:使用指向不可變對象的指針集合。 這是否是一個有效的解決方案取決於您的對象真正代表什么。 但僅僅因為它在Java中運行良好並不意味着它在C ++中運行良好。 在C ++中沒有不可變值對象模式。 這在Java中是一個好主意,在C ++中是一個糟糕的主意。
順便說一句,你的賦值操作符完全是非慣用的並且會泄漏內存。 如果您認真學習C ++,那么您應該閱讀其中一本書 。
(4)不是一種選擇。 隱式聲明的復制賦值運算符將右側對象的每個成員分配給左側對象的同一成員。
編譯器不能為具有const限定數據成員的類隱式生成復制賦值運算符,原因與此無效相同:
const int i = 1;
i = 2;
(2)是有問題的,因為你必須以某種方式克服同樣的問題。
(1)是明顯的解決方案; 如果您的類類型具有const限定數據成員,則它不可分配,並且賦值沒有多大意義。 為什么你說這不是解決方案?
如果您不希望可以分配類類型,則不能在需要其值類型可分配的容器中使用它。 所有C ++標准庫容器都有此要求。
const
並不意味着“這個值只能在特殊情況下改變”。 相反,const意味着“你不允許用它做什么會導致它以任何方式改變(你可以觀察到)”
如果你有一個const限定變量,你不能通過編譯器的命令(以及你自己選擇用const
來限定它)來做任何會導致它改變的事情。 這就是const
作用。 它可能會改變,盡管你是動作,如果它是對非const對象的const引用,或者由於任何其他原因。 如果你作為程序員知道 referant實際上不是常量,你可以用const_cast
將其拋棄並改變它。
但在你的情況下,一個常量成員變量,這是不可能的。 const限定變量不能是對非const的const引用,因為它根本不是引用。
編輯:有關這是什么的驚心動魄的例子,以及為什么你應該在const正確性方面表現自己,讓我們來看看真正的編譯器實際上做了什么。 考慮這個簡短的程序:
int main() {
const int i = 42;
const_cast<int&>(i) = 0;
return i;
}
這就是LLVM-G ++發出的:
; ModuleID = '/tmp/webcompile/_2418_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-linux-gnu"
define i32 @main() nounwind {
entry:
%retval = alloca i32 ; <i32*> [#uses=2]
%0 = alloca i32 ; <i32*> [#uses=2]
%i = alloca i32 ; <i32*> [#uses=2]
%"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
store i32 42, i32* %i, align 4
store i32 0, i32* %i, align 4
store i32 42, i32* %0, align 4
%1 = load i32* %0, align 4 ; <i32> [#uses=1]
store i32 %1, i32* %retval, align 4
br label %return
return: ; preds = %entry
%retval2 = load i32* %retval ; <i32> [#uses=1]
ret i32 %retval2
}
特別感興趣的是行store i32 0, i32* %i, align 4
。 這表明const_cast
成功,我們實際上已經為我初始化的值分配了一個零。
但是對const限定符的修改不會導致可觀察到的變化。 因此,GCC產生一個相當長的鏈,將42放入%0,然后將42放入%1,然后將其再次存儲到%retval中,然后將其加載到%retval2中。 因此,G ++將有這樣的代碼滿足這兩個要求,常量被拋棄,但沒有觀察到的變化到i
,主返回42。
如果您需要一個可以更改的值,例如在標准容器的元素中,那么您不需要const
。
考慮使用private:
使用公共getter和private setter方法的成員。
我將嘗試簡短地將答案捆綁在一起:
主要問題是QList需要賦值賦值運算符,因為它們在內部使用賦值。 因此,他們將實現與界面混合。 因此,雖然你不需要賦值運算符QList但沒有它。 資源
@ 3.有std :: List但它不提供對元素的恆定時間訪問,而QList則提供。
@ 2.可以通過使用復制構造函數和所需屬性創建一個新對象並返回*。 雖然你繞過了const屬性,但它仍然比不使用const更好,因為你會允許容器在這里作弊,但仍然阻止用戶自己這樣做,這是使這個成員保持不變的初衷。
但是要考慮到創建一個重載的賦值運算符會增加代碼的復雜性,並且可能會引入比成員首先解決的更多錯誤。
@ 1.最后,這似乎是最簡單的解決方案。 只要它是私有的,你就必須注意對象本身不會改變它。
@ 4.沒辦法強迫他。 他不知道如何因為變量是常數而且在某些時候他必須這樣做 - this->row = other.row
with int const row;
以前定義的。 即使在這種情況下,const也意味着不變。 一個來源
@ 5 QList沒有這種選擇。
其他方案:
*目前還不確定。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.