Please consider the following code.
struct foo
{
};
template<typename T>
class test
{
public:
test() {}
const T& value() const
{
return f;
}
private:
T f;
};
int main()
{
const test<f开发者_如何学Coo*> t;
foo* f = t.value();
return 0;
}
t
is a const
variable and value()
is a constant member-function which returns const T&
. AFAIK, a const
type is not assignable to a non-const type. But how foo* f = t.value();
compiles well. How this is happening and how can I ensure value()
can be only assigned to const foo*
?
Edit
I found that, this is happening on when templates are used. Following code works as expected.
class test
{
public:
test() {}
const foo* value() const { return f; }
private:
foo* f;
};
int main()
{
const test t;
foo* f = t.value(); // error here
return 0;
}
Why the problem is happening when templates are used?
Because you have two levels of indirection - in your main function, that call to value
returns a reference to a const pointer to a non-const foo
.
This can safely be copied into non-const pointer to a non-const foo
.
If you'd instantiated test
with const foo *
, it would be a different story.
const test<const foo*> t;
foo* f = t.value(); // error
const foo* f = t.value(); // fine
return 0;
Update
From the comment:
value() returns const T& which can only be assigned to another const type. But in this case, compiler is safely allowing the conversion.
Const data can only be read. It cannot be written ("mutated"). But copying some data is a way of reading it, so it's okay. For example:
const int c = 5;
int n = c;
Here, I had some const data in c
, and I copied the data into a non-const variable n. That's fine, it's just reading the data. The value in c
has not been modified.
Now, suppose your foo
had some data in it:
struct foo { int n; };
If I have a non-const pointer to one of those, I can modify the n
value through the pointer. You asked your test
template to store a pointer to a non-const foo
, and then made a const instance of test
. Only the pointer address is constant, therefore. No one can change the address stored in the pointer inside test
, so it cannot be made to point to another object. However, the object it points to can have its contents modified.
Update 2:
When you made your non-template version of the example, you made a mistake. To get it right, you need to substitute foo *
into each place where there's a T
.
const T& value() const
Notice that you have a reference to a const T
there. So the return value will be a reference to something const: a foo *
. It's only the pointer address that can't be modified. The object it points to can have its contents modified.
In your second example, you got rid of the reference part, which changes the meaning and makes the const
modifier apply to the object that the pointer points to, instead of applying to the pointer itself.
Use the following template specialization:
template<typename T>
class test<T*>
{
public:
test() {}
const T* value() const
{
return f;
}
private:
T* f;
};
After including this, g++ says:
d.cpp: In function ‘int main()’:
d.cpp:41: error: invalid conversion from ‘const foo*’ to ‘foo*’
There's nothing wrong in your code, having a const reference to a pointer only means that you can't modify the pointer, but the pointed-to object remains perfectly mutable. If inside your main
function you try to change the address pointed to by the f
member of t
you'll see that you can't: encapsulation is perfectly preserved.
This is the same principle that makes the following code valid:
void foo(std::vector<int *> const & v)
{
*v[0] = 0; // op. [] returns const & to int *
}
People new to C++ are usually surprised by this behavior, because for them a const vector should not allow the modification of its elements. And in fact it doesn't, because the pointer stored in the vector does not change (it keeps pointing to the same address). It's the pointed-to object which is modified, but the vector does not care about that.
The only solution is to do as Amit says and provide a specialization of your class for T*
.
精彩评论