Constant class members, assignment operator and QList

北城余情 提交于 2019-12-06 03:38:36

问题


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.


回答1:


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.




回答2:


(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.




回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/4288190/constant-class-members-assignment-operator-and-qlist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!