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

发布于 2024-10-05 00:55:33 字数 981 浏览 7 评论 0原文

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

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

  1. 不要使用常量成员。 (不是解决方案)
  2. 实现我自己的赋值运算符。
  3. 使用一些其他不需要分配的容器 运算符

这是正确的吗?还有其他优雅的解决方案吗?

另外我想知道我是否可以:

  • (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 所有答案组合在一起回答了我的问题问题。

Please conform if I am correct and tell me whether there is a better solution:

I understand that objects with constant members like int const width; can not be handled by the synthetic assignment operator that is implicitly created by the compiler. But QList (and I suppose std::list, too) needs a working assignment operator. So when I want to use objects with constant members and QList I have three possibilities:

  1. Don't use constant members. (Not a solution)
  2. Implement my own assignment operator.
  3. Use some other container that does not need assignment
    operators

Is that correct? Are there other elegant solutions?

Also I wonder whether I can:

  • (4) Force the compiler to create a assignment operator that deals with constant members! (I don't understand why this is such a big problem. Why is the operator not intelligent enough to use initialization lists internally? Or am I missing something?)
  • (5) Tell QList that I will never use assignment operations in the list.

EDIT: I never assign objects of this class myself. They are only created by the copy constructor or by an overloaded constructor. So the assignment operator is only required by the container not by myself.

EDIT2: This is the assignment operator I created. I am not sure if its correct though. Cell has a two parameter constructor. These parameters set the two constant members with initialization lists. But the object also contains other variable (non const) members.

Cell& Cell::operator=(Cell const& other)
{
 if (this != &other) {
  Cell* newCell = new Cell(other.column(), other.row());
  return *newCell;
 }
 return *this;
}

EDIT3: I found this thread with almost the same question: C++: STL troubles with const class members All answers combined together answered my questions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

差↓一点笑了 2024-10-12 00:55:33

您可能是 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++,您应该阅读其中一本书

You are probably a newcomer to C++ and expect it to behave like Python, Java or C#.

It is quite common to put immutable Java objects into collections. This works because in Java, you do not really put Java objects into collections but merely Java references which refer to Java objects. To be even more precise, a collection internally consists of Java reference variables, and assigning to these Java reference variables does not affect the referenced Java objects at all. They don't even notice.

I deliberately said "Java object", "Java reference" and "Java variable", because the terms "object", "reference" and "variable" have completely different meanings in C++. If you want mutable T variables, you want mutable T objects, because variables and objects are basically the same thing in C++:

A variable is introduced by the declaration of an object. The variable's name denotes the object.

In C++, variables do not contain objects -- they are objects. Assigning to a variable means changing the object (by calling the member function operator=). There is no way around it. If you have an immutable object, then the assignment a = b cannot possibly work without explicitly undermining the type system, and if you do that, then you have effectively lied to your clients about the object being immutable. Making a promise and then deliberately breaking it is rather pointless, isn't it?

Of course you could simply simulate the Java way: use a collection of pointers to immutable objects. Whether or not this is an effective solution depends on what your objects really represent. But just because this works well in Java does not mean it works well in C++. There is no such thing as an immutable value object pattern in C++. It is a good idea in Java and a terrible idea in C++.

By the way, your assignment operator is completely non-idiomatic and leaks memory. If you are serious about learning C++, you should read one of these books.

抱着落日 2024-10-12 00:55:33

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

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

const int i = 1;
i = 2;

(2) 是有问题的,因为您必须克服同样的问题不知何故。

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


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

(4) is not an option. The implicitly declared copy assignment operator assigns each member of the right-hand side object to the same member of the left-hand side object.

The compiler can't implicitly generate a copy assignment operator for a class that has const-qualified data members for the same reason that this is invalid:

const int i = 1;
i = 2;

(2) is problematic since you have to overcome this same issue somehow.

(1) is the obvious solution; if your class type has const-qualified data members, it is not assignable and assignment doesn't make much sense. Why do you say that this is not a solution?


If you don't want your class type to be assignable then you can't use it in a container that requires that its value type is assignable. All of the C++ standard library containers have this requirement.

风渺 2024-10-12 00:55:33

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 成功,我们实际上在 i 初始化的值上分配了一个零。

但是对 const 限定符的修改不会导致可观察到的变化。因此,GCC 产生了一个相当长的链,将 42 放入 %0,然后将该 42 放入 %1,然后再次将其存储到 %retval,然后将其加载到 %retval2。因此,G++ 将使这段代码满足这两个要求,const 被丢弃,但 i 没有明显的变化,main 返回 42。


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

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

const does not mean "this value can only change under special circumstances." Rather, const means "Nothing you're allowed to do with it will cause it to change in any way (that you could observe)"

If you have a const qualified variable, you aren't allowed, by fiat of the compiler (and your own choice to qualify it with const in the first place), to do anything that would cause it to change. That's what const does. It might change in spite of you're actions, if it is a const reference to a non-const object, or for any of a range of other reasons. If you as the programmer know that the referant is not actually constant, you can cast it away with const_cast and change that.

But in your case, a constant member variable, this isn't possible. The const qualified variable is cannot be a const reference to non-const, because it's not a reference at all.

Edit: for a thrilling example of what this is all about and why you should behave yourself with regards to const correctness, lets have a look at what a real compiler actually does. Consider this short program:

int main() {
  const int i = 42; 
  const_cast<int&>(i) = 0; 
  return i;
}

And here's what LLVM-G++ emits:

; 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
}

Of particular interest is the line store i32 0, i32* %i, align 4. This indicates that the const_cast was successful, we actually assigned a zero over the value that i had been initialized to.

But modifications to const qualifieds cannot result in an observable change. Thus GCC produces a fairly lengthy chain of putting 42 into %0, and then loating that 42 into %1, then storing it again into %retval, and then loading it into %retval2. Thus, G++ will have this code satisfy both requirements, the const was cast away but there was no observable change to i, main returns 42.


If you need a value that can be changed, for instance in the elements of a standard container, then you do not need const.

Consider using private: members with public getter and private setter methods.

枕梦 2024-10-12 00:55:33

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

主要问题是 QList 要求存在赋值运算符,因为它们内部使用赋值。因此,他们将实现与接口混合在一起。因此,尽管您不需要赋值运算符,但没有它 QList 将无法工作。 来源

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

@ 2. 可以通过使用复制构造函数和所需属性创建一个新对象并返回它*。虽然你规避了 const 属性,但它仍然比根本不使用 const 更好,因为你允许容器在这里作弊,但仍然阻止用户自己执行此操作,这就是使该成员常量的初衷。

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

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

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

@ 5 QList 没有此选项种类。

其他解决方案:

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

*目前不确定这一点。

I'll try to bundle the answers in short:

The main problem is that QList requires the assignment operator to be present because they internally use assignment. Thus they mix implementation with interface. So although YOU don't need the assignment operator QList won't work without it. source

@ 3. There is std::List but it doesn't offer constant time access to elements, while QList does.

@ 2. It is possible by creating a new object with the copy constructor and the desired properties and returning it*. Although you circumvent the const property it is still better than using no const at all because you would allow the container to cheat here but still prevent users to do this themselves which was the original intention of making this member constant.

But take into account that creating an overloaded assignment operator adds to the complexity of the code and might introduce more errors than the const-ing of the members would solve in the first place.

@ 1. In the end this seems to be the easiest solution. As long as it's private you just have to pay attention that the object doesn't change it itself.

@ 4. No way to force him. He wouldn't know how because the variable is constant and at some point he would have to do this->row = other.row with int const row; previously defined. And const means constant even in this case. one source

@ 5 QList has no options of this kind.

Additional solutions:

  • Use pointer to objects instead of pure objects

*Not sure about this at the moment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文