Apparently, a const member function is still allowed to change data that the class member are pointing to. Here's an example of what I mean:
class MyClass
{
public:
MyClass();
int getSomething() const;
private:
int* data;
};
// ... data = new int[10];, or whatever
int MyClass::getSomething() const
{
data[4] = 3; // this is allowed, even those the function is const
return data[4];
}
I'd prefer if this was not allowed. How should I define "data" so that "getSomething() const" isn't allowed to change it? (but so that non-const functions are allowed to change it.) Is there some kind of "best practice" for this? Perhaps std::vector?
In a const member function, the type of data changes from int* to int *const:
int * const data;
which means, it's the pointer which is const in the const member function, not the data itself the pointer points to. So you cannot do the following:
data = new int[100]; //error
as it's attempting to change the pointer itself which is const, hence disallowed, but the following is allowed:
data[0] = 100; //ok
Because changing the content doesn't change the pointer. data points to same memory location.
If you use std::vector<int>, then you can achieve what you want. In fact, vector solves this problem, along with the memory management issues, therefor use it:
class MyClass
{
public:
MyClass();
int getSomething() const;
private:
std::vector<int> data;
};
MyClass::MyClass() : data(10) {} //vector of size 10
int MyClass::getSomething() const
{
data[4] = 3; // compilation error - this is what you wanted.
return data[4];
}
Avoid non-RAII design as much as you can. RAII is superior solution to memory management issues. Here, with it, you achieve what you want. Read this:
Resource Acquisition Is Initialization (RAII)
There is no way to do what you want. Your const member function can't change the value of data (the address it points to), but there is nothing about not changing the contents it points to as you have already noticed. Using a std::vector would help, since within a const member function you would actually have a const vector, not being able to call any of its mutable functions.
The reason you should use std::vector is actually because you want to store a collection of ints, and not an int pointer, so why not use it?
It would also solve your const'ness issue.
Here's how it works: declaring a method const will make all class members const. In your case, that means that in the method scope, your member data will become a constant pointer to an int. That means you can change the int (also meaning array members) as long as data points to the same location. Using std::vector, data would become a const std::vector, on which you could only call const functions. So yes, you should use std::vector.
Related
I am using a function from an external library (here represented by the printer-function).
By chance, I noticed, that I can pass a class member from a const member function like done by the print()-function. Even if I compile using
g++ -Wall -Wextra -Wpedantic -Werror -o test test.cpp
I don't obtain an error.
This seems to be horribly wrong, because effectively I am modifying the object by calling a const member function. Why is the compiler not complaining?
Here is the code:
#include <iostream>
using namespace std;
void printer(void* data) {
static_cast<char*>(data)[0] = '1';
cout << static_cast<char*>(data) << endl;
}
void setUp(char* data, int length) {
for(int i=0; i<length; i++)
data[i] = '0';
data[length] = '\0';
}
class Test {
private:
char* data;
public:
Test(int length) {
data = new char(length+1);
setUp(this->data, length);
}
~Test() {
delete data;
}
void print() const {
printer(this->data);
}
};
int main() {
Test T(3);
T.print();
return 0;
}
My best guess is, that the compiler only guarantees, that the pointer variable is not modified, i.e. not pointing to a different physical address after the function call. But I also noticed, that the compiler throws errors when I try something similar with a std::vector<char> instead of char* and a modified print-function:
void print() const {
printer(this->myVector.data());
}
So if my guess is correct, why is it not possible to do something similar with the pointer obtained by data() of a STL vector?
You have a char* data member.
In the context of a const-qualified method, that means the member may not be mutated.
The member is the pointer. Changing a pointer means changing where it points.
Just to be clear, the effective qualified type inside the method is
char * const
and not
const char *
Which is all to confirm that you're absolutely right that
My best guess is, that the compiler only guarantees, that the pointer variable is not modified, i.e. not pointing to a different ... address after the function call.
However, for the vector, you want to call data on something that again has effective qualified type std::vector<char> const& ... so you need the const-qualified overload of std::vector::data, and that returns const char*. This is what you expected in the first place, but it's not the same behaviour as the raw pointer.
The reason for the difference is that the vector was written to express ownership of that data, so a const vector should have const contents. Raw pointers may own the data they point at, but there's no way to express this, and no way to have different const behaviour for owning and non-owning raw pointers. This is one of the may reasons to avoid raw pointers where possible.
This is a common way of allowing a const method to change data. The immutable thing in Test is the pointer address data; which is not altered by print. You are nonetheless free to mutate what the pointer is addressing.
Another way round a const method is to declare a member as being mutable. Arguably, ensuring an internal string attribute is e.g. null terminated for printing could be considered a legitimate use of mutable.
See this piece from Kate Gregory at CppCon 2017 where she provides an interesting example (a method is designed to be const but a cache is added later):
https://youtu.be/XkDEzfpdcSg?t=854
Also see this question for a lively discussion on the topic:
Does the 'mutable' keyword have any purpose other than allowing the variable to be modified by a const function?
Apparently, a const member function is still allowed to change data that the class member are pointing to. Here's an example of what I mean:
class MyClass
{
public:
MyClass();
int getSomething() const;
private:
int* data;
};
// ... data = new int[10];, or whatever
int MyClass::getSomething() const
{
data[4] = 3; // this is allowed, even those the function is const
return data[4];
}
I'd prefer if this was not allowed. How should I define "data" so that "getSomething() const" isn't allowed to change it? (but so that non-const functions are allowed to change it.) Is there some kind of "best practice" for this? Perhaps std::vector?
In a const member function, the type of data changes from int* to int *const:
int * const data;
which means, it's the pointer which is const in the const member function, not the data itself the pointer points to. So you cannot do the following:
data = new int[100]; //error
as it's attempting to change the pointer itself which is const, hence disallowed, but the following is allowed:
data[0] = 100; //ok
Because changing the content doesn't change the pointer. data points to same memory location.
If you use std::vector<int>, then you can achieve what you want. In fact, vector solves this problem, along with the memory management issues, therefor use it:
class MyClass
{
public:
MyClass();
int getSomething() const;
private:
std::vector<int> data;
};
MyClass::MyClass() : data(10) {} //vector of size 10
int MyClass::getSomething() const
{
data[4] = 3; // compilation error - this is what you wanted.
return data[4];
}
Avoid non-RAII design as much as you can. RAII is superior solution to memory management issues. Here, with it, you achieve what you want. Read this:
Resource Acquisition Is Initialization (RAII)
There is no way to do what you want. Your const member function can't change the value of data (the address it points to), but there is nothing about not changing the contents it points to as you have already noticed. Using a std::vector would help, since within a const member function you would actually have a const vector, not being able to call any of its mutable functions.
The reason you should use std::vector is actually because you want to store a collection of ints, and not an int pointer, so why not use it?
It would also solve your const'ness issue.
Here's how it works: declaring a method const will make all class members const. In your case, that means that in the method scope, your member data will become a constant pointer to an int. That means you can change the int (also meaning array members) as long as data points to the same location. Using std::vector, data would become a const std::vector, on which you could only call const functions. So yes, you should use std::vector.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Why isn't the const qualifier working on pointer members on const objects?
Consider the following class that has a pointer member int *a. The const method constMod is allowed by the compiler even though it modifies the pointer data. Why doesn't the compiler make the pointer data const in the context of the const method? If a was just an int we wouldn't be allowed to modify it in a const method.
class ConstTest
{
public:
ConstTest(int *p): a(p) {}
void constMod() const {
++(*a);
}
int *a;
};
I'm using g++ on linux.
Inside constMod(), the declaration of a is treated as:
int *const a;
which means the pointer has a constant value, not what it points to. It sounds like you are expecting it to be treated as:
const int *a;
which is different.
The pointer itself is not modified, only the data pointed. Even if it sounds strange from a human point of view, from a compiler point of view, none of the member of the class are altered.
It's just an ownership issue... there's no way the compiler can know if the pointed-to object is logically part of the object or not, so it's left for the programmer to police such issues. const members are allowed to perform operations with side effects, as long as they don't modify their own apparent value. It's not really any different from letting them call say std::cout::operator<<() or some other non-const function....
What is const in the above case is the pointer itself. You are not allowed to do ++a;. However, it doesn't prevent you from modifying the data being pointed to.
I believe because here you are trying to change the value where datamember is pointing to.
If you try to modify the data member it would be error.
Meaning, you'll get error if you make a point to something else instead of changing the value
const member functions will not allow you to modify it's members.
In your example, you have a pointer that points an int.
And in the const method, you are modifying the value that it is pointed to but not the pointer itself.
Try giving, ++a, which will actually modify the pointer value and will not be allowed in your const method.
Consider
const T immutable_object0;
const T immutable_object1;
const T* mutable_view = ...a condition... ? &immutable_object0 : &immutable_object1;
// then later you reseat
mutable_view = ...another condition... ? &immutable_object0 : &immutable_object1;
or
const int immutable_data[120]
const int* mutable_view = immutable_data;
for(const int* end = immutable_data + 120; mutable_view != end; ++mutable_view) {
// can't modify *mutable_view
}
It's because pointers don't always have ownership. In some circumstances pointers are views to an object (first example) or are iterators into a raw array (second example). For those cases it doesn't make sense to restrict the operations available on the pointers just because the data pointed to is immutable (or seen as immutable).
The first example is a bit contrived but a valid version of it is when you use a pointer member to implement object association and you don't want the hassle of a reference member. Sometimes the pointed-to types happen to be const.
What does const really mean? Read-only seems to encapsulate its meaning for me, but, I'm not sure I'm right.
If read-only and const are different, could someone tell me why?
What prompted this question was this answer where he states const "just" means read-only in C. I thought that's all const meant, regardless of whether it was C or C++. What does he mean?
For an answer to the specific differences in const in C vs C++, I've created a new question: How does "const" differ in C and C++? as per R..'s suggestion.
By declaring a variable as const you indicate compiler that you have no intentions of modifying that variable. But it does not mean others don't have! It's just to allow some optimization and to be notified by a compile error (note, that it's mostly compile error, while const == ReadOnly would mean runtime errors).
const does not mean read only, because you can write const volatile, that would mean it could change by itself anytime, but I have no intentions to modify it.
EDIT: here is a classical example: consider I'm writing the code that reads current time from a memory-mapped port. Consider that RTC is mapped to memory DWORD 0x1234.
const volatile DWORD* now = (DWORD*)0x1234;
It's const because it's a read-only port, and it's volatile because each time I will read it it will change.
Also note that many architectures effectively make global variables declared as const read-only because it's UB to modify them. In these cases UB will manifest itself as a runtime-error. In other cases it would be a real UB :)
Here is a good reading: http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html
The compiler won't allow something declared as const to be modified. It is as you say.
It's mostly used in function prototypes to inform the user that a function won't touch this or that when passed pointers. It also works as kind of failsafe for yourself.
A lot of people are telling you that const means you can't modify it. That is patently false. const can trivially be cast away. Note this snippet:
void foo(const int *somevalue)
{
int *p = (int*) somevalue;
*p = 256; // OMG I AM EVIL!!!!11
}
Your compiler will not stop you from doing this. So, what then is the purpose of const? I'd call it more of a suggestion. It reminds you as you look at function prototypes of the contract that your functions expect. Your compiler will yell at you if you carelessly break it. (But not if you intentionally break it, as with the above cast.)
In some cases the standard intentionally breaks const. Note the return values of strstr for example: by definition it will return some offset into the const buffer you provide it... But the returned value is not const. Why? Well, this would break meaningfully using the return value of strstr on a non-const buffer.
Two byte for byte identical (except for the comments) minimal case examples...
First in C, gcc will emit a warning...
/* Function taking a pointer to an array of
two read only integers.*/
void a( const int (* parray)[2]);
void b(void)
{
int array[2] = {1,2};
const int crray[2] = {1,2};
/* C reserves the right to stash this in a read-only location.*/
a( &array);
/* warning: passing argument 1 of ‘a’ from incompatible pointer type*/
a( &crray); /* OK!*/
}
Now the same thing in C++... g++ is quite happy with it.
// Function taking a pointer to an array
// of two integers which it promises not to modify.
// (Unless we cast away it's constness ;-P)
void a( const int (* parray)[2]);
void b(void)
{
int array[2] = {1,2};
const int crray[2] = {1,2};
a( &array); // C++ has no problem with this.
a( &crray); // OK!
}
const char * hello_1{ "Hello!" };
const char hello_2[]{ "Hello!" };
char * ptr{};
// take away the const-nes
// ptr = (char *)hello_1;
// *ptr = '*'; <-- write access violation
// hello_1 is in a read only memory
// take away the const-nes
ptr = (char *)hello_2;
*ptr = '*'; // <-- OK
// hello_2 is modifiable
Pointers point to memory, and char * points to memory in the data segment which is read only. The difference between char * and char [] is that while both are declared the same way on the data segment, char [] is treated as readable because it is pushed onto the stack if it is used.
C++ allows for the definition of const member functions. const member functions are the only functions that be called on const objects. Also, const member functions cannot modify any data members of a class (unless the data member marked mutable).
class Foo
{
int data;
void Bar();
void ConstBar() const;
};
void Foo::ConstBar() const
{
// Error! cannot modify i as ConstBar is a const member function.
// i = 0;
}
// Usage:
const Foo foo_instance;
// Error! cannot call non-const member on a const object.
// foo_instance.Bar();
// OK
foo_instance.ConstBar();
Const means that a pointer or reference cannot be used for a write or read-modify-write operation without casting away const. It does NOT mean what the C++ standard tries to claim it means (the C++ standard is just wrong on this).
A variable defined like this:
/* auto */ int const x = 1;
is patently NOT read-only since otherwise it could not be initialised. Rather, the kind of variable x is "reference const to int" (and NOT reference to const int) or alternatively lvalue const of int. Note carefully the "const" is associated with a pointer or reference it has nothing to do with the storage, nor the type of the value residing in that storage.
This is rather unfortunate because the contract provided by const is extremely weak, and in particular fails to allow caching of a pointed at or referred to memory location, precisely because it does NOT mean immutable storage.
The bottom line is: const is an access modifier associated with a symbolic reference or pointer which is used by the programmer to allow the symbol provider to establish an obligation on the symbol client, or for the symbol client to promise the symbol provider it does not modify storage via this symbol (for example a function accepting a pointer const to int promises not to modify the pointed at int).
This has nothing to do with variables:
int const *p = (int*)malloc(sizeof(int));
and clearly little to do with storage (malloc'ed store is always writable).
Instead you should think of const as a way of communicating invariants, obligations or requirements between parts of the program, put in place by the programmer for the programmers purposes, and propagated by the type system. Unfortunately the type system isn't sound and fails to properly propagate constness correctly:
X *last;
struct X { int a; X() : a(0) { last=this; } };
X const x; // x is const?
last->a = 1; //really ??
IMHO the only opportunity a compiler has to make store immutable is for actual constants such as string literals (maybe) or static (global) storage. Automatic, heap, and temporary storage cannot be made read-only in practice.
I wrote a little "lazy vector" class (or, delayed vector) which is supposed to look like a std::vector and usable wherever a std::vector is used, but it loads its elements "lazily", i.e. it will load element n (and possibly a few more) from disk whenever someone accesses element n. (The reason is that in my app, not all elements fit into memory.)
Here is this LazyVector class, but there is a problem with const member functions that use such a vector, see below.
template<class T>
class LazyVector {
std::vector<T> elems_;
void fetchElem(unsigned n){
// load the n-th elem from disk into elems_ etc
}
public:
const T& operator[](unsigned n) const {
fetchElem(n); // ERROR: ... discards qualifiers
return elems_[n];
}
T& operator[](unsigned n) {
fetchElem(n);
return elems_[n];
}
// and provide some other std::vector functions
};
As I said, there is a problem when a const member function asks for an element of the LazyVector. By nature of the LazyVector, accessing an element is not const, i.e. it will change the vector vec below, which is forbidden in this context. The foo member function must be const and cannot be changed. How can I solve this?
class Foo {
LazyVector<const std::string*> vec;
void fct(int n) const { // fct must be const
const std::string* str = vec[n];
// do something with str
}
};
You can either use mutable member data or const_cast in the implementation of your LazyVector class. Thus you can create the illusion of constness needed by your consuming class without actually being const.
Use the mutable keyword on the elems_ data member.
The const operator is used to show that the object is logically const.
The fact that your data is on disk is neither here nor there your object is not changing state so you can delegate the work for actually holding the data to another object a cache (Where the data is stored is an implementation details and not part of the objects state).
class LazyVector
{
public:
int const& operator[](int index) const
{
data->fetchElement(index);
return data->get(index);
}
private:
std::auto_ptr<LazyDataCache> data;
};
Here data is a pointer (a smart pointer but still a pointer). As long as the pointer does not change you are not changing the cost-ness of the LazyVector. But you can still call non const methods on the object pointed at by data (remember it is the pointer that is const NOT the object pointed at).
For such things, the mutable keyword is for. Put your cache as a mutable object into your class. That is because your cache seems to not change the logical content/state of your object (i.e the elements of your vector or the size of it do not change).
const methods do not state they don't physically change your object. They state that they won't change the abstract value of your object. Implementation details that are abstracted away may still be changed by const functions.
The mutable is for this kind of cases. Make your vector mutable or add a mutable cache member that contains some sort of cache entries.
Read the What are the semantics of a const member function answer by Anthony Williams.
Declare elems_ as mutable:
mutable std::vector<T> elems_;
There's other stuff you can do, but that's the supported way of doing it.
Edit: Another way of doing this is to add another member and set it in the constructor:
std::vector<T> *mutable_elems_;
mutable_elems_(&elems_)
A crude way of doing this would be
LazyVector* p = const_cast<LazyVector*>(this);
p->fetch();
I guess there will be better way of doing this. But this will work.