I've encountered similar code piece today and it got me wondering. I did a little experiment myself as shown below.
Why does the first stuff function with shared pointer allow to modify the value while seconds one doesn't?
#include <memory>
void stuff(const std::shared_ptr<int> &var)
{
*var = 5;
}
void stuff(const int* &var)
{
*var = 5;
}
int main()
{
auto a = std::make_shared<int>();
stuff(a);
int* b;
stuff(b);
return 0;
}
These two types:
const std::shared_ptr<int>
const int* (aka int const*)
are fundamentally different:
The first one is a const pointer to an int;
The second one is a pointer to a const int.
The shared_ptr equivalent of the version that doesn't permit modification of the pointee would be std::shared_ptr<const int>.
The raw pointer equivalent of the version that does permit modification of the pointee would be int* const.
Ability to modify the pointer, and ability to modify the pointed-to thing, are different things.
Related
Would it be undefined behavior to change where a pointer points, when its data is const? Example:
const char* p = "foo";
p = "boo";
I believe that this is not UB, because the pointer itself is not const and I'm not modifying the "foo" object.
Extra question: and altering not const data of a const pointer? Would be UB? Example:
char* const p = "foo";
(*(char**)&p) = (char*)malloc(strlen(p));
I believe that this is not UB, because the pointer itself is not const and I'm not modifying the "foo" object.
This is correct. The pointer is not const so you can change it to point to something else if you want. In this case it won't cause a meory leak but remember that if the pointer points to data allocated with new and it is the only pointer to that data then you need to call delete before reassigning the pointer otherwise you'll have a meory leak.
Extra question: and removing the constness of pointer? Would be UB?
It is only UB if you try to modify a const object you removed const from, which you do in this case. Just removing const is okay, and sometimes needed, but you are never allowed to modify the object unless it was not const to begin with. For example the following is legal since foo is not const.
int foo = 42;
void bar(int const& baz) { const_cast<int&>(baz) = 21; }
int main()
{
bar(foo);
}
on the other hand
const int foo = 42;
void bar(int const& baz) { const_cast<int&>(baz) = 21; }
int main()
{
bar(foo);
}
is not legal as foo is const
The code in the first snippet is 100% correct. You have a pointer to const p, which you repoint to something else. All good and in mint condition.
The second piece of code is ill-formed. You can't modify an object after removing the constness, if original object was const-qualified (which string literal is).
Would it be undefined behavior to change where a pointer points, when its data is const? Example:
const char* p = "foo";
p = "boo";
I believe that this is not UB, because the pointer itself is not const and I'm not modifying the "foo" object.
Extra question: and altering not const data of a const pointer? Would be UB? Example:
char* const p = "foo";
(*(char**)&p) = (char*)malloc(strlen(p));
I believe that this is not UB, because the pointer itself is not const and I'm not modifying the "foo" object.
This is correct. The pointer is not const so you can change it to point to something else if you want. In this case it won't cause a meory leak but remember that if the pointer points to data allocated with new and it is the only pointer to that data then you need to call delete before reassigning the pointer otherwise you'll have a meory leak.
Extra question: and removing the constness of pointer? Would be UB?
It is only UB if you try to modify a const object you removed const from, which you do in this case. Just removing const is okay, and sometimes needed, but you are never allowed to modify the object unless it was not const to begin with. For example the following is legal since foo is not const.
int foo = 42;
void bar(int const& baz) { const_cast<int&>(baz) = 21; }
int main()
{
bar(foo);
}
on the other hand
const int foo = 42;
void bar(int const& baz) { const_cast<int&>(baz) = 21; }
int main()
{
bar(foo);
}
is not legal as foo is const
The code in the first snippet is 100% correct. You have a pointer to const p, which you repoint to something else. All good and in mint condition.
The second piece of code is ill-formed. You can't modify an object after removing the constness, if original object was const-qualified (which string literal is).
I'm curious why the compiler doesn't complain if I hand over a const-pointer to a non-const-pointer as parameter in a constructor of a class that itself, which of course is at construction time not const.
#include <iostream>
class A
{
public:
A(int* v) : v(v) {}
int* v;
};
class B
{
public:
B() : o(23), v(&o) {}
const A cc() const
{
return A(v); //expected const to non-cosnt error here, but seems valid
}
int o;
int* v;
};
int main() {
B b;
const B* bc = &b;
A a = bc->cc();
*(a.v) = 25; // here I'm changing an originally const value
std::cout << b.o << std::endl;
return 0;
}
With defining cc() as const I expected the line where the return value is initialized an error message about converting a const to a non-const. The code-snippet compiles fine and in the end I get the output "25" even so that should have been const.
The A constructor expects a copy of a pointer instead of a reference. It is absolutely legal to copy a int * const into an int*. Therefore, no error occurs.
You should use references instead of pointers when you want a consistent behavior regarding constness. By the way, returning const objects is a bad idea.
You're confusing int const* (or const int*) and int* const.
Copying a const object into a non-const new object is fine, whether that object is a pointer or not. That's all you're doing here; the pointer itself is const (it's int* const), so you can pretty much do what you like with a copy of it.
What is not ok is copying a int const* into a int*, because now you're saying the thing being pointed to has magically lost its const-protection.
constness of an object does not propagate to constness of the pointee of a pointer member. The pointer becomes const but the pointee stays non-const. Even if the pointer is const, you can easily obtain a non-const pointer by simply copying the pointer which is exactly what happens as the pointer is passed by value.
I'm struggling a bit trying to work out how to pass const pointers to classes around using shared pointers. Normally you just declare a pointer to an object const and you can no longer change any members in it, however if the class is using shared pointers the const does not seem to apply.
Here is a compilable example of the problem. In processA(), it should not be possible to add another element to the vector as the object is supposed to be const.
What am I doing wrong?
#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>
struct C {
int value;
};
struct B {
std::vector<C> vectorOfC;
};
struct A {
boost::shared_ptr<B> b;
};
typedef boost::shared_ptr<A> PtrA;
void processA(boost::shared_ptr<const A> a)
{
// This should not be possible because 'a' points to const
C c2;
c2.value = 456;
a->b->vectorOfC.push_back(c2);
for (std::vector<C>::const_iterator
i = a->b->vectorOfC.begin(); i != a->b->vectorOfC.end(); i++
) {
std::cout << i->value << std::endl;
}
}
int main(void)
{
C c;
c.value = 123;
PtrA a(new A);
a->b.reset(new B);
a->b->vectorOfC.push_back(c);
processA(a);
return 0;
}
All this stuff about shared_ptr is just a red herring.
You have pointer to a const object that has a pointer to a non-const object.
You can of course modify the latter object.
I think you're misunderstanding how const works with pointers.
const T * doesn't declare a const pointer, it declares a pointer to const. I.e. the pointer is mutable, but the thing(s) it points at are not.
When you add const to a pointer, you actually get T * const, which is a const pointer to mutable object(s), i.e. you can still change the pointee through the pointer.
In the case of std::shared_ptr (or boost::shared_ptr) adding const leaves you with const std::shared_ptr<T>, i.e. you cannot change the shared_ptr, but you can change the pointee, because the shared_ptr does not point to an immutable object.
To rectify this, you want to use std::shared_ptr<const T>, which is a mutable shared_ptr to a const object.
You can get around this by:
Making your boost::shared_ptrs pointers to const, or, if in certain instances it's acceptable to mutate the pointee
Using overloads of a getter (one overload being const, the other being mutable) to properly return boost::shared_ptr<const T> in the proper case, or, if callers don't actually need the boost::shared_ptr itself
Using overloads of a getter (one overload being const, the other being mutable) to return references to T, with the const overload returning const T & and the mutable overload returning T &
I would pass T*& pointer, when I am intending to change the pointed value inside the function:
void foo(char *&p)
{
p = (b == true)? new char[10] : 0;
}
But I am not able to get what is the use case for T* const& kind of pointer (since that pointer is not changeable)? I mean why should not I pass simply T* const ?
void foo(char* const &p); // p is not changeable
void foo(char* const p); // p is not changeable
You would use a T* const & as a parameter if the value of the pointer object might be changed by something external to your function and you wanted to be able to observe changes to the value of the pointer object or if you wanted to store a reference or pointer to the pointer object for later reading.
A T* parameter (equivalent to T* const as a function parameter) just gives you a copy of the pointer object, a snapshot of its value when it was passed to your function.
void foo( char* const& ptr )
{
char* p1 = ptr; // initial value
global_fn(); // ptr might be changed
char* p2 = ptr; // new value of ptr
}
vs
void foo2( char* ptr )
{
char* p1 = ptr; // initial value
global_fn(); // ptr can't be changed, it's local to this function
char* p2 = ptr; // will be the same as p1
}
Technically, even the function itself might change the value of the pointer to which it is passed a reference.
E.g.
char* p;
std::ptrdiff_t foo( char* const& ptr )
{
++p;
return p - ptr; // returns 0, would return 1 if the parameter was by value
}
int main()
{
char test[] = "Hello, world!";
p = test;
foo( p );
}
The difference is realistically nil. const references are used to prevent copying of expensive-to-copy or, in generic code, uncopyable types, but since pointers are trivial, it's negligible and you may as well take by value.
I think a simpler example would illustrate the point that Charles Bailey is making. Let's remove the issue of the pointer part of it, because for this question it is irrelevant. So your question basically becomes:
void foo(const int &p); // p is not changeable
void foo(const int p); // p is not changeable
Do you see more clearly how it works? Yes, the local variable "p" cannot be assigned to in both cases. And yes, neither piece of code will affect the variable in the calling scope. But in the former example p could be a reference to a variable (non-const) int that can be changed while the latter is an argument passed by value which could not be changed. (Actually the const in the second example has no effect on anything outside the function, so it is kind of superfluous. Same with the second example in the question.)