In C++, Can a Class with a Const Data Member Not Have a Copy Assignment Operator

In C++, can a class with a const data member not have a copy assignment operator?

In C++, a class with a const data member may have a copy constructor.

#include <iostream>

class A
{
private:
const int k_;
public:
A(int k) : k_(k) {}
A() = delete;
A(const A& other) : k_(other.k_) {}

int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
A a1(5);
A a2(a1);

std::cout << "a1.k_ = " << a1.get_k() << "\n";
std::cout << "a2.k_ = " << a2.get_k() << "\n";
}

Output:

a1.k_ = 5
a2.k_ = 5

In C++, a class with a const data member may not use the default assignment operator.

class A
{
private:
const int k_;
public:
A(int k) : k_(k) {}
A() = delete;
A(const A& other) : k_(other.k_) {}

int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
A a1(5);
A a2(0);

a2 = a1;
}

Yields a compile time error:

const_copy_constructor.cpp: In function ‘int main(int, char**)’:
const_copy_constructor.cpp:18:10: error: use of deleted function ‘A& A::operator=(const A&)’
18 | a2 = a1;
| ^~
const_copy_constructor.cpp:1:7: note: ‘A& A::operator=(const A&)’ is implicitly deleted because the default definition would be ill-formed:
1 | class A
| ^
const_copy_constructor.cpp:1:7: error: non-static const member ‘const int A::k_’, can’t use default assignment operator

In C++, a class with a const data member may use a non-default assignment operator as long as you don't attempt to change the const data member, but you better think long and hard about what it means to use this assignment operator if one of the underlying members cannot be modified.

class A
{
private:
const int k_;
public:
A(int k) : k_(k) {}
A() = delete;
A(const A& other) : k_(other.k_) {}

A& operator=(A const& other)
{
// do nothing
return *this;
}

int get_k() const { return k_; }
};

int main(int argc, char** argv)
{
A a1(5);
A a2(0);

a2 = a1;
}

Yields no compile time errors.

Const Class Member Copy Constructor

When you declare a const member, the compiler does not generate a default assignment operator (it has no clue what to do with this member during assignment, after all, it is const ?), you will have to write the assignment operator yourself.

Note:

  • pass you parameters by reference to const.

assignment of class with const member

C++03 requires that elements stored in containers be CopyConstructible and Assignable (see §23.1). So implementations can decide to use copy construction and assignment as they see fit. These constraints are relaxed in C++11. Explicitly, the push_back operation requirement is that the type be CopyInsertable into the vector (see §23.2.3 Sequence Containers)

Furthermore, C++11 containers can use move semantics in insertion operations and do on.

C++: STL troubles with const class members

As AndreyT pointed out, under these circumstances assignment (mostly) doesn't make a lot of sense. The problem is that vector (for one example) is kind of an exception to that rule.

Logically, you copy an object into the vector, and sometime later you get back another copy of the original object. From a purely logical viewpoint, there's no assignment involved. The problem is that vector requires that the object be assignable anyway (actually, all C++ containers do). It's basically making an implementation detail (that somewhere in its code, it might assign the objects instead of copying them) part of the interface.

There is no simple cure for this. Even defining your own assignment operator and using const_cast doesn't really fix the problem. It's perfectly safe to use const_cast when you get a const pointer or reference to an object that you know isn't actually defined to be const. In this case, however, the variable itself is defined to be const -- attempting to cast away the constness and assign to it gives undefined behavior. In reality, it'll almost always work anyway (as long as it's not static const with an initializer that's known at compile time), but there's no guarantee of it.

C++ 11 and newer add a few new twists to this situation. In particular, objects no longer need to be assignable to be stored in a vector (or other collections). It's sufficient that they be movable. That doesn't help in this particular case (it's no easier to move a const object than it is to assign it) but does make life substantially easier in some other cases (i.e., there are certainly types that are movable but not assignable/copyable).

In this case, you could use a move rather than a copy by adding a level of indirection. If your create an "outer" and an "inner" object, with the const member in the inner object, and the outer object just containing a pointer to the inner:

struct outer { 
struct inner {
const double coeff;
};

inner *i;
};

...then when we create an instance of outer, we define an inner object to hold the const data. When we need to do an assignment, we do a typical move assignment: copy the pointer from the old object to the new one, and (probably) set the pointer in the old object to a nullptr, so when it's destroyed, it won't try to destroy the inner object.

If you wanted to badly enough, you could use (sort of) the same technique in older versions of C++. You'd still use the outer/inner classes, but each assignment would allocate a whole new inner object, or you'd use something like a shared_ptr to let the outer instances share access to a single inner object, and clean it up when the last outer object is destroyed.

It doesn't make any real difference, but at least for the assignment used in managing a vector, you'd only have two references to an inner while the vector was resizing itself (resizing is why a vector requires assignable to start with).

Are const class members useful when the assignment operator is overloaded?

Here's your misconception which is causing this issue:

[..] which is not changed by the methods of the class

The member variable is changed by a method of your class, the assignment operator. Including the one synthesized by the compiler. If you mark a member variable as const, this expresses that this variable will (should not!) change its value during the lifetime of the object. So clearly, assigning a new value to the object violates this statement. So if you indeed don't want the member to change, just don't make it const.

C++ copy assignment operator behaviour

You have not copied the data member stamina inside the copy assignment operator. So just add stamina = rhs.stamina; inside the copy assignment operator and you'll get your expected output. So the modified definition of operator= looks like:

Player& operator=(const Player& rhs)
{
cout << "Copy assignment operator called " << endl;
stamina = rhs.stamina; //ADDED THIS
return *this;
}

Also, note that in your original code snippet, since the data member stamina is not initilized and so it has indeterminate value. Using this uninitialized variable(which you do when you wrote p2.get_stamina();) is undefined behavior.

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

So the output that you're seeing is a result of undefined behavior. And as i said don't rely on the output of a program that has UB.

So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.

To solve this more serious problem just use in-class initilaizer for data member stamina.

class Player {
//other members here
private:
int stamina = 0; //USE IN-CLASS INITIALIZER
};

1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

C++ User Defined Class Copy Assignment Missing?

You have in class A a non-static data member declared as

const std::string

that is with the qualifier const. In this case the implicit copy assignment operator is defined by the compiler as deleted.

const member and assignment operator. How to avoid the undefined behavior?

Your code causes undefined behavior.

Not just "undefined if A is used as a base class and this, that or the other". Actually undefined, always. return *this is already UB, because this is not guaranteed to refer to the new object.

Specifically, consider 3.8/7:

If, after the lifetime of an object
has ended and before the storage which
the object occupied is reused or
released, a new object is created at
the storage location which the
original object occupied, a pointer
that pointed to the original object, a
reference that referred to the
original object, or the name of the
original object will automatically
refer to the new object and, once the
lifetime of the new object has
started, can be used to manipulate the
new object, if:

...

— the type of the original object is
not const-qualified, and, if a class
type, does not contain any non-static
data member whose type is
const-qualified or a reference type,

Now, "after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied" is exactly what you are doing.

Your object is of class type, and it does contain a non-static data member whose type is const-qualified. Therefore, after your assignment operator has run, pointers, references and names referring to the old object are not guaranteed to refer to the new object and to be usable to manipulate it.

As a concrete example of what might go wrong, consider:

A x(1);
B y(2);
std::cout << x.c << "\n";
x = y;
std::cout << x.c << "\n";

Expect this output?

1
2

Wrong! It's plausible you might get that output, but the reason const members are an exception to the rule stated in 3.8/7, is so that the compiler can treat x.c as the const object that it claims to be. In other words, the compiler is allowed to treat this code as if it was:

A x(1);
B y(2);
int tmp = x.c
std::cout << tmp << "\n";
x = y;
std::cout << tmp << "\n";

Because (informally) const objects do not change their values. The potential value of this guarantee when optimizing code involving const objects should be obvious. For there to be any way to modify x.c without invoking UB, this guarantee would have to be removed. So, as long as the standard writers have done their job without errors, there is no way to do what you want.

[*] In fact I have my doubts about using this as the argument to placement new - possibly you should have copied it to a void* first, and used that. But I'm not bothered whether that specifically is UB, since it wouldn't save the function as a whole.



Related Topics



Leave a reply



Submit