Re: const-correctness loopholes
noir wrote:
Il 15/04/2011 22.15, Kai-Uwe Bux ha scritto:
noir wrote:
int main ( void ) {
{
int i = 5;
example x (&i );
*x.p = 6;
std::cout<< i<< "\n";
}
{
int i = 5;
example const x (&i );
// *x.p = 6; // fails to compile! deep const
}
{
int i = 5;
example const x (&i );
int_ptr p ( x.p ); // initializing from x, which is const!
*p = 6;
std::cout<< i<< "\n";
}
}
This very interesting, but I would not call it a loophole. Rather, a
careless implementation of the const_correct_ptr class.
If I understand correctly, in this line:
int_ptr p ( x.p ); // initializing from x, which is const!
the compiler is calling the default constructor for const_correct_ptr,
that would look something like this:
const_correct_ptr(const_correct_ptr const& rhs)
{
the_ptr = rhs.the_ptr;
}
Of course, this bypasses any const-correct interface or whatever, and
copies the raw pointer to the new non-const object, so game over.
Correct.
However, with a little more effort, I believe the programmer of the
const_correct_ptr class can enforce the right behaviour, very unlike the
const_cast that effectively throws a monkey wrench on the whole
const-correctness issue (and looks to me as a real loophole).
Let's see. It is going to be an interesting exercise to work out better
implementations of const_correct_ptr<>.
I can see two ways to close the "loophole" you presented:
Solution 1
----------
If the original class is supposed to share the pointer, than we can
define a const correct accessor:
const_pointer getThePointer() const { return the_ptr; }
pointer getThePointer() { return the_ptr; }
and replace the default copy constructor with this one:
const_correct_ptr(const_correct_ptr const& rhs)
{
the_ptr = rhs.getThePointer(); // use const-correct accessor
}
This does not (correctly) compile anymore (failure to convert from
"const int *" to "int *" in the line above)
Unfortunately, this makes copying impossible for all objects, const or not:
template < typename T >
class const_correct_ptr {
typedef T* pointer;
typedef T& reference;
typedef T const * const_pointer;
typedef T const & const_reference;
pointer the_ptr;
const_pointer getThePointer() const { return the_ptr; }
pointer getThePointer() { return the_ptr; }
public:
const_correct_ptr ( const_correct_ptr const & other )
: the_ptr( other.getThePointer() )
{}
const_correct_ptr ( pointer ptr = 0 )
: the_ptr ( ptr )
{}
pointer operator-> ( void ) {
return ( the_ptr );
}
const_pointer operator-> ( void ) const {
return ( the_ptr );
}
reference operator* ( void ) {
return ( *the_ptr );
}
const_reference operator* ( void ) const {
return ( *the_ptr );
}
};
typedef const_correct_ptr< int > int_ptr;
int main ( void ) {
{
int_ptr p1 ( new int ( 5 ) );
int_ptr p2 ( p1 ); // fails to compile
int_ptr const p3 ( p1 ); // fails to compile
}
{
int_ptr const p1 ( new int ( 5 ) );
int_ptr const p2 ( p1 ); // fails to compile
int_ptr p3 ( p1 ); // this is the only line that _should_
// not compile.
}
}
Equivalently, you could just declare the copy-constructor of
const_correct_ptr<> private.
Of course, is also means that any class containing a const_correct_ptr<>
member is not copy-constructible. Often, this is not acceptable.
Solution 2
----------
If the original class is NOT supposed to share the pointer, then we can
simply replace the default shallow-copy constructor with a deep-copy
constructor:
const_correct_ptr(const_correct_ptr const& rhs)
{
the_ptr = new T(*the_ptr);
}
This is not a solution either. Consider the original problem, you have a
class
class example {
T * ptr_member;
...
};
and you want to replace that by
class example {
const_correct_ptr<T> ptr_member;
...
};
Now, if you do a deep copy in the copy constructor, you could equivalently
do
class example {
T value_member; // has deep copy automatically!
...
};
Given that there probably were good reasons for the pointer member, the
const_correct_ptr<T> would not be a reasonable substitute.
There is one exception: if the pointer was only used for polymorphism.
However, in that case a better implementation of a deep-copy smart pointer
is needed: one that does not slice.
Best,
Kai-Uwe Bux