常量类成员,赋值运算符和QList

时间:2010-11-26 19:52:49

标签: c++ qt assignment-operator qlist class-constants

如果我是正确的,请说明并告诉我是否有更好的解决方案:

我理解具有int const width;等常量成员的对象不能由编译器隐式创建的合成赋值运算符处理。但是QList(我想std :: list)也需要一个工作赋值运算符。因此,当我想使用具有常量成员和QList的对象时,我有三种可能性:

  1. 不要使用常数成员。 (不是解决方案)
  2. 实现我自己的赋值运算符。
  3. 使用其他不需要分配的容器 运营商
  4. 这是对的吗?还有其他优雅的解决方案吗?

    我也想知道我是否可以:

    • (4)强制编译器创建一个处理常量成员的赋值运算符! (我不明白为什么这是一个很大的问题。为什么操作员不够智能,不能在内部使用初始化列表?或者我错过了什么?)
    • (5)告诉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 troubles with const class members所有答案结合在一起回答了我的问题。

4 个答案:

答案 0 :(得分:8)

您可能是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 ++,你应该阅读one of these books

答案 1 :(得分:3)

(4)不是一个选项。隐式声明的复制赋值运算符将右侧对象的每个成员分配给左侧对象的同一成员。

编译器不能为具有const限定数据成员的类隐式生成复制赋值运算符,原因与此无效相同:

const int i = 1;
i = 2;

(2)是有问题的,因为你必须以某种方式克服同样的问题。

(1)是明显的解决方案;如果您的类类型具有const限定数据成员,则它不可分配,并且赋值没有多大意义。为什么你说这不是解决方案?


如果您不希望可以分配类类型,则不能在需要其值类型可分配的容器中使用它。所有C ++标准库容器都有此要求。

答案 2 :(得分:3)

const并不意味着“这个价值只能在特殊情况下改变”。更确切地说,const意味着“你不允许用它做什么会导致它以任何方式改变(你可以观察到)”

如果你有一个const限定变量,你不能通过编译器的命令(以及你自己的选择首先用const限定它)来做任何会导致它的事情。更改。这就是const的作用。它可能会改变,尽管你是动作,如果它是对非const对象的const引用,或者由于任何其他原因。如果您作为程序员知道,那么该反叛者实际上并不是常数,您可以使用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 ++将使这个代码满足两个要求,const被抛弃但是i没有可观察到的变化,主要返回42.


如果您需要一个可以更改的值,例如在标准容器的元素中,那么您不需要const

考虑将private:成员与公共getter和私有setter方法一起使用。

答案 3 :(得分:2)

我会尝试简短地将答案捆绑在一起:

主要问题是QList需要赋值赋值运算符,因为它们在内部使用赋值。因此,他们将实现与界面混合。因此,虽然你不需要赋值运算符QList但没有它。 source

@ 3.有std :: List但它不提供对元素的恒定时间访问,而QList则是。

@ 2.可以通过使用复制构造函数和所需属性创建一个新对象并返回*。虽然你绕过了const属性,但它仍然比不使用const更好,因为你会允许容器在这里作弊但仍然阻止用户自己这样做,这是使这个成员保持不变的初衷。

但是考虑到创建一个重载的赋值运算符会增加代码的复杂性,并且可能会引入比成员首先解决的更多错误。

@ 1.最后,这似乎是最简单的解决方案。只要它是私有的,你就必须注意对象本身不会改变它。

@ 4.没办法强迫他。他不知道如何因为变量是常数而且在某些时候他必须this->row = other.row并且先前定义了int const row;。即使在这种情况下,const也意味着不变。 one source

@ 5 QList没有此类选项。

其他解决方案:

  • 使用指向对象的指针而不是纯对象

*目前还不确定。