5

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.

Community
  • 1
  • 1
problemofficer
  • 435
  • 4
  • 15

4 Answers4

8

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.

Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
3

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

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Please see my EDIT. So how do you handle objects with const members? I want to use the constness to prevent unwanted change just like you would make members private to prevent errors. Is such a major feature "disabled" when using std containers? I can't believe it. – problemofficer Nov 26 '10 at 20:11
  • @problemofficer: I don't understand the problem: if you actually never want the value of the member variable to change, then the class is obviously not assignable. On the one hand, you want the data member to be immutable (since you want it to be `const`) but you also want it to be mutable (since you want to be able to assign a new value to it). Const-qualified data members are rarely worth the costs. – James McNellis Nov 26 '10 at 20:14
  • 1
    I don't want to assign anything. I simply want to store these objects in a container. And the container complained that it needs the assignment operator. If it wasn't for the container I wouldn't even know that my object does not have an assignment operator because, as I said, I don't need it and will not use it. – problemofficer Nov 26 '10 at 20:18
  • @problemofficer: You can't use those containers because they require your objects to be assignable. While it's technically possible to implement a linked list that never uses `operator=`, containers requiring copy & assignments semantics usually use it internally. – André Caron Nov 26 '10 at 20:22
  • 2
    Wouldn't storing a pointer or a reference to your object a workaround to be able to use an stl container? – Falmarri Nov 26 '10 at 20:38
  • @Falmarri: why don't you suggest that as an answer? – André Caron Nov 26 '10 at 20:45
  • @problemofficer: If you want to store objects of your class type in a container that requires its value type to be assignable, then yes, you do need the class type to be assignable. There's not a whole lot more to it than that. – James McNellis Nov 26 '10 at 21:11
  • @Falmarri: A reference would not work because a reference is neither an object nor assignable. A pointer has the disadvantage of requiring additional indirection to access the object. – James McNellis Nov 26 '10 at 21:12
  • @James: yes a reference would not work, but a pointer would, and it is the neatest way of accomplishing what problemofficer wants. – lijie Nov 26 '10 at 21:17
  • 3
    @problemofficer: QList is not an standard container. As a matter of fact, the standard list (`std::list`) can handle non-assignable types, it is the Qt implementation of a list the one that requires assignability. – David Rodríguez - dribeas Nov 26 '10 at 21:19
  • @lijie, @James McNellis: the extra cost of indirection for a pointer is most probably acceptable for the correctness of the solution. At any rate, another option (that also requires the indirection) would be using `boost::ref` (or `std::ref` in the upcoming standard). Now, a question that arises is who is in control of the lifetime of the object, and the reference adaptors require lifetime to be controlled externally. – David Rodríguez - dribeas Nov 26 '10 at 21:22
  • @David Rodríguez: I already upvoted your comment. Unfortunatelly lists don't offer constant time access to elements, while QList do. – problemofficer Nov 26 '10 at 21:39
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.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • Sorry, but -1: `const` has nothing to do with code generation. Plenty of cases exist where there are `mutable` members, or `const_cast`, which don't allow the compiler to make such assumptions (because it is not computable if a pointer may or pay not be pointing to a member which is considered `const` any any one point in time). There are some assumptions the compiler can make with respect to constant members, but it must be able to prove that the object is actually const, not based upon hints in the source program. – Billy ONeal Nov 26 '10 at 20:22
  • The code it can generate relates to the observable changes that an operation on a `const` qualified variable may cause. That is to say, no change may be observable. Therefore operations on `const` variables may be reordered or elided safely. – SingleNegationElimination Nov 26 '10 at 20:26
  • @TokenMacGuy: That is not true. It's perfectly legal to cast `const`-ness away. It is impossible for the compiler to prove in the general case whether or not any particular variable had const casted away. Therefore, compilers cannot use `const` as the basis for such optimizations. `const` is about compile time checks and has absolutely nothing to do with how the program behaves at runtime. – Billy ONeal Nov 26 '10 at 20:32
  • 1
    @Billy: But mutating const objects when constness is cast away is undefined behavior? – UncleBens Nov 26 '10 at 20:40
  • @Billy: I suggest you take a good look at the standard. There *are* lost of things the compiler can assume when you write `const` and it is *not* always legal to cast `const`-ness away. – André Caron Nov 26 '10 at 20:41
  • @UncleBens: Nope. It's often undefined behavior in specific cases (for example, modifying the result of `std::basic_string<>::c_str()`), but that's imposed by the libraries, not by the language itself. – Billy ONeal Nov 26 '10 at 20:43
  • @Billy: get yourself a copy of the standard. It's legal for the compiler to put `const` objects in read-only storage. "Un`const`ing" that value would lead to an access violation. – André Caron Nov 26 '10 at 20:44
  • @Andre: I have a copy of the standard. I just read the entire section on const casting. Nowhere is undefined behavior mentioned. It says "depending on the type of object, casting away const **may** produce undefined behavior', but again, that's based on the particular object one is dealing with, not upon the language itself. The only exception to this I can think of are character constants (i.e. string literals), which may be stored in read only memory. – Billy ONeal Nov 26 '10 at 20:47
  • 2
    @Billy: I would think the only instance where casting const away is guaranteed not to produce undefined behaviour is when the const pointer/reference refers to an originally unconst object. – lijie Nov 26 '10 at 20:53
  • @lijie: What you think is just fine. I, however, care only about what the standard says. According to the standard, these programs do not produce undefined behavior: http://codepad.org/ypxdohpK http://codepad.org/h3CSMd3w , and both demonstrate changing the value of a variable which was declared `const`. – Billy ONeal Nov 26 '10 at 20:57
  • 3
    @Billy: What about the section on cv-qualifiers: "Except that any class member declared mutable (_dcl.stc_) can be modified, any attempt to modify a const object during its lifetime (_basic.life_) results in undefined behavior." - As to your example, undefined behavior does not mean that the program may not behave as intended. – UncleBens Nov 26 '10 at 21:00
  • @UncleBens: I stand corrected on my first example. My second example still demonstrates the behavior however. – Billy ONeal Nov 26 '10 at 21:04
  • @Billy: The quoted section in particular mentions `mutable` members that can always be modified. – UncleBens Nov 26 '10 at 21:06
  • @Billy: there's no casting away of const in the second example, so the point is kind of moot. – lijie Nov 26 '10 at 21:13
  • @lijie: You're still modifying a variable declared `const`. Just because you're not casting it away does not make the point any less valid. `const` is not about runtime, it is about compile time. – Billy ONeal Nov 26 '10 at 21:38
  • @Billy: How about this snippet of code? `const int i = 42; const_cast(i) = 0; std::cout << i << std::endl;` Even though we apparently changed `i` to `0`, it still prints `42` on my system :) Trying to change `i` invokes UB because `i` *really* is defined as `const`. – fredoverflow Nov 26 '10 at 21:49
  • @Billy: no we are not talking about modifying a `const` variable. Note that the discussion from your second comment has been about _casting_ const away and modifying an originally `const` variable. And `const` is not about runtime. It only exists at compile time. – lijie Nov 27 '10 at 02:39
2

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.

Community
  • 1
  • 1
problemofficer
  • 435
  • 4
  • 15