I am trying to implement the use of a C++ library within my project that has not used const modifiers on its access functions. Up until now I have been using const in all of my code but this new library is causing two main problems:
Functions where the arguments are passed as const references cannot use the argument's access functions if these arguments are of a type defined by the library.
Classes with member objects of types defined by the library cannot use the access functions of these objects within a const function.
What is the best way to overcome this issue? The easiest solution would be to simply remove all use of const from my code but that would be quite frustrating to do.
Additional info: In this case I do have access to the source code and can see that the access functions do not modify anything. I omitted this information as I was interested in the more general case as well. For my scenario, const_cast
appears to be the way to go
PS The library writer is not evil! It is more a bit of rough and ready code that he has kindly open sourced. I could ditch the library and use something more professional as others have noted. However, for this small time-constrained project, the simplicity of the interface to this library has made it the best choice.
The const member functions are the functions which are declared as constant in the program. The object called by these functions cannot be modified. It is recommended to use const keyword so that accidental changes to object are avoided. A const member function can be called by any type of object.
const member functions Declaring a member function with the const keyword specifies that the function is a "read-only" function that doesn't modify the object for which it's called. A constant member function can't modify any non-static data members or call any member functions that aren't constant.
A const object can be created by prefixing the const keyword to the object declaration. Any attempt to change the data member of const objects results in a compile-time error. When a function is declared as const, it can be called on any type of object, const object as well as non-const objects.
const at the end of the function means it isn't going to modify the state of the object it is called up on ( i.e., this ).
How easy is it to tell whether the functions in the library actually modify anything or not?
If it's easy to tell, and they don't, then you can const_cast
your const pointer/reference to non-const and call the library function. You might want to throw a wrapper around the library classes to do this for you, which is tedious and verbose but gets that code out of your classes. This wrapper could perhaps be a subclass that adds some const accessors, depending whether the way you use the library class allows that to work.
If it's hard to tell, or they do modify things, then you need to use non-const instances and references to the library classes in your code. mutable
can help with those of type (2), but for those of type (1) you just need to pass non-const arguments around.
For an example of why it might be hard, consider that the library author might have written something like this:
struct Foo {
size_t times_accessed;
int value;
int get() {
++times_accessed;
return value;
}
};
Now, if you const_cast
a const
instance of Foo
and call get()
, you have undefined behavior[*]. So you have to be sure that get
really doesn't modify the object it's called on. You could mitigate this a bit, by making sure that you never create any const instances of Foo
, even though you do take const references to non-const instances. That way, when you const_cast
and call get
you at least don't cause UB. It might make your code confusing, that fields keep changing on objects that your functions claim not to modify.
[*] Why is it undefined behavior? It has to be, in order that the language can guarantee that the value of a const
object never changes in a valid program. This guarantee allows the compiler to do useful things. For example it can put static const
objects in read-only data sections, and it can optimize code using known values. It also means that a const
integer object with a visible initializer is a compile-time constant, which the standard makes use of to let you use it as the size of an array, or a template argument. If it wasn't UB to modify a const object, then const objects wouldn't be constant, and these things wouldn't be possible:
#include <iostream>
struct Foo {
int a;
Foo(int a) : a(a) {}
};
void nobody_knows_what_this_does1(const int *p); // defined in another TU
void nobody_knows_what_this_does2(const int *p); // defined in another TU
int main() {
const Foo f(1);
Foo g(1);
nobody_knows_what_this_does1(&f.a);
nobody_knows_what_this_does2(&g.a);
int x;
if (std::cin >> x) {
std::cout << (x / f.a); // Optimization opportunity!
std::cout << (x / g.a); // Cannot optimize!
}
}
Because f
is a const object, and hence f.a
is a const object, the optimizer knows that f.a
has value 1 when it's used at the end of the function. It could, if it chose, optimize away the division. It doesn't know the same thing about g.a
: g
is not a const object, a pointer to it has been passed into unknown code, so its value might have changed. So if you're the author of nobody_knows_what_this_does1
or nobody_knows_what_this_does2
, and you're thinking of const_cast
ing p
and using it to modify its referand, then you can only do it if you somehow know that the referand is non-const. Which normally you don't, so normally you don't use const_cast
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With