 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Oskar Enoksson Guest
|
Posted: Sun Feb 22, 2004 1:40 am Post subject: How to give access to const-interface of a base-class? |
|
|
When working on a reference-counting smart pointer-class I stumbled on a
tricky situation I don't know how to solve:
I want to be able to implicitly convert a RefPtr<T> & into a const
RefPtr<const T> &. Such a conversion operator would allow me to
implement the standard rules of constness and pointers.
Ideally I would like to do have a "const-inheritance" as follows:
template<typename T>
struct RefPtr<T>;
template<typename T>
struct RefPtr<const T> {
/* ... */
};
template<typename T>
struct RefPtr: public const RefPtr<const T> {
/* ... */
};
Now, I know that this kind of "const-inheritance" is not allowed (but I
wonder why, because I have seen many other cases where this would have
been useful and I can't see any problems with allowing such a feature in
c++). Anyway, the next thing I tried is private inheritance and a
conversion operator member:
template<typename T>
struct RefPtr<T>;
template<typename T>
struct RefPtr<const T> {
/* ... */
};
template<typename T>
struct RefPtr: private RefPtr<const T> {
operator const RefPtr<const T> &() const { return *this; }
/* ... */
};
It turns out that the compilers I have tried (gcc 3.3.1 and Intel icc 8.0)
do accept this code, but the overloading of the implicit conversion
operator is silently ignored, so later on when trying to compile the line
RefPtr<int> i;
RefPtr<const int> j;
j = i;
I get compilation error (gcc 3.3.1):
error: `RefPtr<const int>' is an inaccessible base of `RefPtr<int>'
So, the only solution I have found is using membership instead of
inheritance as follows:
template<typename T>
struct RefPtr<T>;
template<typename T>
struct RefPtr<const T> {
/* ... */
};
template<typename T>
struct RefPtr: private RefPtr<const T> {
operator const RefPtr<const T> &() const { return const_ptr; }
/* ... */
private:
RefPtr<const T> const_ptr;
};
All member functions of RefPtr<const T> must now be re-implemented in
RefPtr<T> as wrappers calling the same member functions in the const_ptr
member object. This seems like an ugly solution, and potentially
inefficient (depending on the compilers optimization capabilities to
inline all wrapper functions and efficiently eliminate any unnecessary
copying of arguments and return values).
So, my question is, is there a more clever way than my solution?
Is there any particular reason for not allowing const inheritance?
Is there any particular reason for allowing but ignoring conversion
operators to protected base classes? IMHO this should either be disallowed
or it should be allowed and work as expected.
Thank you.
Regards
/Oskar
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jonathan Turkanis Guest
|
Posted: Sun Feb 22, 2004 10:57 am Post subject: Re: How to give access to const-interface of a base-class? |
|
|
"Oskar Enoksson" <enok (AT) lysator (DOT) liu.se> wrote
| Quote: |
When working on a reference-counting smart pointer-class I stumbled
on a
tricky situation I don't know how to solve:
I want to be able to implicitly convert a RefPtr<T> & into a const
RefPtr<const T> &. <snip
|
Why? reference counted smart pointers are meant to be easy to copy. If
you have a RefPtr
define one and initialize it with the RefPtr<T> (assuming you have
defined conveting constructors). You will have two distinct smart
pointers, but they will control the same resource. What more do you
need?
Jonathan
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Oskar Enoksson Guest
|
Posted: Sun Feb 22, 2004 5:29 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Jonathan Turkanis wrote:
| Quote: | "Oskar Enoksson" <enok (AT) lysator (DOT) liu.se> wrote in message
news:Pine.GSO.4.51L2.0402212037140.13741 (AT) nazgul (DOT) lysator.liu.se...
When working on a reference-counting smart pointer-class I stumbled
on a
tricky situation I don't know how to solve:
I want to be able to implicitly convert a RefPtr<T> & into a const
RefPtr<const T> &. <snip
Why? reference counted smart pointers are meant to be easy to copy. If
you have a RefPtr
define one and initialize it with the RefPtr<T> (assuming you have
defined conveting constructors). You will have two distinct smart
pointers, but they will control the same resource. What more do you
need?
|
But copying a reference-counting pointer will generate a count-up and a
count-down of the reference-counter, right? And this will (in many
cases) force the compiler to invalidate some integers cached in
registers and re-read them from memory because of potential aliasing
problems?
I just think it would be nice to avoid copying of reference pointers
wherever possible (for efficiency-reasons). For instance in trivial
member access functions, such as the following:
struct Bar;
struct Foo {
const RefPtr<Bar> & GetBar() { return bar; }
const RefPtr<const Bar> & GetBar() const { return bar; }
private:
RefPtr<Bar> bar;
};
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Baraff Guest
|
Posted: Mon Feb 23, 2004 11:51 am Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Oskar Enoksson <enok_tabortmig (AT) lysator (DOT) liu.se> wrote
| Quote: | Jonathan Turkanis wrote:
"Oskar Enoksson" <enok (AT) lysator (DOT) liu.se> wrote in message
news:Pine.GSO.4.51L2.0402212037140.13741 (AT) nazgul (DOT) lysator.liu.se...
When working on a reference-counting smart pointer-class I stumbled
on a
tricky situation I don't know how to solve:
I want to be able to implicitly convert a RefPtr<T> & into a const
RefPtr<const T> &. <snip
Why? reference counted smart pointers are meant to be easy to copy. If
you have a RefPtr
define one and initialize it with the RefPtr<T> (assuming you have
defined conveting constructors). You will have two distinct smart
pointers, but they will control the same resource. What more do you
need?
But copying a reference-counting pointer will generate a count-up and a
count-down of the reference-counter, right?
|
Yes. But if you follow your argument to its logical conclusion, then
RefPtr<Derived> should inherit from RefPtr<Base>, if Derived inherits
from Base. After all, if you have a RefPtr<Derived>, if would be real
nice to pass it to a function which wants a RefPtr<Base> without
making an extra copy, just to avoid the bump-up/bump-down.
IMHO, this is more trouble than it's worth. vector<Base> and
vector<Derived> have no inheritance relationship, nor should
RefPtr<Base> and RefPtr<Derived>, and from that, neither should
RefPtr<const Base> and RefPtr<Base>.
Please note that I would *love* it if there was an easy way to pass a
RefPtr<Derived> to a function wanting a RefPtr<Base> without making an
extra copy. In fact, I also code functions as
void Func(const RefPtr<Base>& ptr) {
...
}
and was surprised to find out that there was a lot of ref-count
increment/decrementing going on, until someone pointed out to me it
was coming from passing in a RefPtr<Derived>, and the compiler needing
to generate a temporary. I thought for a while about how to avoid it
and decided there was no good way.
Feel free to convince me otherwise. :)
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Tue Feb 24, 2004 1:39 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
The following works in VC .NET 2003 atleast:
template<typename T_>
struct Ptr
{
template<typename U_> operator Ptr<U_>&()
{
sizeof(static_cast<U_*>((T_*)0)); // check for proper ptr conversion
return reinterpret_cast<Ptr(*this);
}
};
struct A{};
struct B: A {};
struct C {};
void foo1(const Ptr<const B>&) {}
void foo2(const Ptr<A>&) {}
void foo3(const Ptr<const A>&) {}
void foo4(const Ptr<C>&) {}
int main()
{
Ptr<B> p;
foo1(p); // ok
foo2(p); // ok
foo3(p); // ok
foo4(p); // error
return 0;
}
Jarkko
"David Baraff" <deb (AT) pixar (DOT) com> wrote
| Quote: | Oskar Enoksson <enok_tabortmig (AT) lysator (DOT) liu.se> wrote
Jonathan Turkanis wrote:
"Oskar Enoksson" <enok (AT) lysator (DOT) liu.se> wrote in message
news:Pine.GSO.4.51L2.0402212037140.13741 (AT) nazgul (DOT) lysator.liu.se...
When working on a reference-counting smart pointer-class I
stumbled
on a
tricky situation I don't know how to solve:
I want to be able to implicitly convert a RefPtr<T> & into a const
RefPtr<const T> &. <snip
Why? reference counted smart pointers are meant to be easy to copy.
If
you have a RefPtr
define one and initialize it with the RefPtr<T> (assuming you have
defined conveting constructors). You will have two distinct smart
pointers, but they will control the same resource. What more do you
need?
But copying a reference-counting pointer will generate a count-up and a
count-down of the reference-counter, right?
Yes. But if you follow your argument to its logical conclusion, then
RefPtr<Derived> should inherit from RefPtr<Base>, if Derived inherits
from Base. After all, if you have a RefPtr<Derived>, if would be real
nice to pass it to a function which wants a RefPtr<Base> without
making an extra copy, just to avoid the bump-up/bump-down.
IMHO, this is more trouble than it's worth. vector<Base> and
vector<Derived> have no inheritance relationship, nor should
RefPtr<Base> and RefPtr<Derived>, and from that, neither should
RefPtr<const Base> and RefPtr<Base>.
Please note that I would *love* it if there was an easy way to pass a
RefPtr<Derived> to a function wanting a RefPtr<Base> without making an
extra copy. In fact, I also code functions as
void Func(const RefPtr<Base>& ptr) {
...
}
and was surprised to find out that there was a lot of ref-count
increment/decrementing going on, until someone pointed out to me it
was coming from passing in a RefPtr<Derived>, and the compiler needing
to generate a temporary. I thought for a while about how to avoid it
and decided there was no good way.
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Baraff Guest
|
Posted: Wed Feb 25, 2004 3:12 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
"Jarkko Lempiainen" <jarkko (AT) videotron (DOT) ca> wrote
| Quote: | The following works in VC .NET 2003 atleast:
template<typename T_
struct Ptr
{
template
{
sizeof(static_cast<U_*>((T_*)0)); // check for proper ptr conversion
return reinterpret_cast<Ptr(*this);
}
};
struct A{};
struct B: A {};
struct C {};
void foo1(const Ptr<const B>&) {}
void foo2(const Ptr<A>&) {}
void foo3(const Ptr<const A>&) {}
void foo4(const Ptr<C>&) {}
int main()
{
Ptr<B> p;
foo1(p); // ok
foo2(p); // ok
foo3(p); // ok
foo4(p); // error
return 0;
}
Jarkko
|
Your solution will fail in the case of multiple inheritance -- if the
raw address of Derived and Base differ, what you propose will hose
you.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Oskar Enoksson Guest
|
Posted: Thu Feb 26, 2004 12:19 am Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Jarkko Lempiainen wrote:
| Quote: | The following works in VC .NET 2003 atleast:
template<typename T_
struct Ptr
{
template
{
sizeof(static_cast<U_*>((T_*)0)); // check for proper ptr conversion
return reinterpret_cast<Ptr(*this);
}
};
|
Actually I finally implemented something along this line, but I (humbly)
think I have an even better solution. Above, you return a non-const
reference, this is bad because you may then assign that reference with a
pointer to an unrelated derived type.
Secondly, your way of checking for proper ptr conversion will allow
casting that are not normally allowed implicit casts. My way (below)
will work exactly in those cases where the corresponding built-in
pointer implicit cast works.
The only remaining problem I see is that the result of reinterpret_cast
between pointers to two unrelated instantiations of a template class is
in theory undefined (AFIK). However, I would be very interested to know
about any compiler with any optimization flag combination that will
break this code. I have some arguments for that such a compiler must
either have some almost supernatural strict-alias-based optimization
method, or it must be extremely irrational.
struct ReferenceCounter;
template<typename T>
class rp
{
ReferenceCounter *ref_counter; // Reference counter object
T *p; // Pointer to data
template<typename TT>
static inline void Dummy(TT * const &) {}
public:
template<typename TT>
operator const rp<TT> &() const
{
Dummy(p); // check for proper ptr onversion
return reinterpret_cast<const rp(*this);
}
/* ... */
};
For anyone interested I published the complete implementation and
documentation at http://www.lysator.liu.se/~enok/software/rp/ I'd be
glad to hear peoples opinion on this. How "dangerous" is it?
/Oskar
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Fri Feb 27, 2004 2:11 am Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Yes, you are absolutely right, but you could prevent such casts.
MI is evil anyway ;)
Jarkko
"David Baraff" <deb (AT) pixar (DOT) com> wrote
| Quote: |
Your solution will fail in the case of multiple inheritance -- if the
raw address of Derived and Base differ, what you propose will hose
you.
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Fri Feb 27, 2004 2:44 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
[email]deb (AT) pixar (DOT) com[/email] (David Baraff) wrote in message
news:<37483324.0402242020.5a7e8246 (AT) posting (DOT) google.com>...
| Quote: | "Jarkko Lempiainen" <jarkko (AT) videotron (DOT) ca> wrote in message
news:<ZMA_b.34436$FO1.1354881 (AT) weber (DOT) videotron.net>...
The following works in VC .NET 2003 atleast:
template<typename T_
struct Ptr
{
template
{
sizeof(static_cast<U_*>((T_*)0)); // check for proper ptr conversion
return reinterpret_cast<Ptr(*this);
}
};
struct A{};
struct B: A {};
struct C {};
void foo1(const Ptr<const B>&) {}
void foo2(const Ptr<A>&) {}
void foo3(const Ptr<const A>&) {}
void foo4(const Ptr<C>&) {}
int main()
{
Ptr<B> p;
foo1(p); // ok
foo2(p); // ok
foo3(p); // ok
foo4(p); // error
return 0;
}
Your solution will fail in the case of multiple inheritance -- if the
raw address of Derived and Base differ,
|
Which may be the case even in the case of single inheritance, at least
according to the standard.
| Quote: | what you propose will hose you.
|
His proposition contains undefined behavior. I don't know what it is
intended to do, but it doesn't do it.
--
James Kanze GABI Software mailto:kanze (AT) gabi-soft (DOT) fr
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Fri Feb 27, 2004 6:21 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Obviously you need both const & non-const versions of the implicit
conversion op. I just didn't bother to write both (: I don't see what
would be the problem with unrelated type though, because you
always check for the valid conversion. Could you explain with an
example how does it allow conversion that are not normally allowed?
I don't really see any advantages in the alternative you propose.
Infact your code _allows_ incorrect casts (i.e. the foo4 passes
with your code, which is wrong).
Jarkko
"Oskar Enoksson" <enok_tabortmig (AT) lysator (DOT) liu.se> wrote
| Quote: |
Actually I finally implemented something along this line, but I (humbly)
think I have an even better solution. Above, you return a non-const
reference, this is bad because you may then assign that reference with a
pointer to an unrelated derived type.
Secondly, your way of checking for proper ptr conversion will allow
casting that are not normally allowed implicit casts. My way (below)
will work exactly in those cases where the corresponding built-in
pointer implicit cast works.
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Oskar Enoksson Guest
|
Posted: Sat Feb 28, 2004 12:15 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Jarkko Lempiainen wrote:
| Quote: | Obviously you need both const & non-const versions of the implicit
conversion op. I just didn't bother to write both (: I don't see what
would be the problem with unrelated type though, because you
always check for the valid conversion. Could you explain with an
example how does it allow conversion that are not normally allowed?
|
struct A {};
struct B1: public A {};
struct B2: public A {};
Ptr<B1> p1;
Ptr<A> &rp1(p1); // Cast as you want to allow it
rp1 = new B2();
Now p1 points to an object of type B2 even though it is of type Ptr<B1>.
This is not good! This is also the reason for the following implicit
cast rules for built-in pointer types:
foo1(A * const &p) {}
foo2(A * &p) {}
int main() {
B1 *p;
foo1(p);
// Works! B1 * & can be implicitly cast to a A * const &
foo2(p);
// Fails! B1 * & cannot be implicitly cast to a A * &
}
| Quote: | I don't really see any advantages in the alternative you propose.
Infact your code _allows_ incorrect casts (i.e. the foo4 passes
with your code, which is wrong).
|
There was an error in my code ... sorry ... replace the call Dummy(p);
with Dummy<TT>(p); Then foo4 will fail (as it should).
The advantage is that my call Dummy<TT>(p) requires an implicit cast
from T * & to TT * const & (to match argument in Dummy<TT>(p) ) while
your static_cast<U_*>((T_*)0) is more "brutal". Static cast is more
powerful than implicit cast, for instance you can cast not only to
ancestor type but to sibling type:
struct A {};
struct B1: public A {};
struct B2: public A {};
Ptr<B1> pb1 = new B1();
Ptr<B2> pb2 = pb1;
// Allowed by your static_cast, but not what we want!
I hope I'm thinking right ... do you agree?
/Oskar
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Sat Feb 28, 2004 12:18 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
I still have to see the compiler where the solution I
proposed doesn't work (: I think it's good to try to
follow the standard, but being a purist will probably
hurt you more than taking the hit of such an unlikely
pathological scenario.
Yet again you can prevent casts which require
pointer adjustment, if not in compile-time, atleast
in run-time. And if you like, you can always place
such non-standard constructs into nonstandard.h (:
I don't do this in my own smart pointer impl. though,
because if you really have to pass the ownership of
an object, you are probably ready to take the hit of
pump-up/pump-down ref counting anyway. Atleast
that's what I have found out in practice.
Jarkko
<kanze (AT) gabi-soft (DOT) fr> wrote
| Quote: | deb (AT) pixar (DOT) com (David Baraff) wrote in message
Your solution will fail in the case of multiple inheritance -- if the
raw address of Derived and Base differ,
Which may be the case even in the case of single inheritance, at least
according to the standard.
what you propose will hose you.
His proposition contains undefined behavior. I don't know what it is
intended to do, but it doesn't do it.
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Mon Mar 01, 2004 12:24 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Ah, yes, you are absolutely right. For some reason I was completely
blind to the downcasting scenario (: You might want to have slight
improvement to your code though and change the return type of
Dummy to int and do:
sizeof(Dummy<TT>(p));
to make sure it's compile-time expression and doesn't end up to
the run-time code in debug builds/deep inlining.
Jarkko
"Oskar Enoksson" <enok_tabortmig (AT) lysator (DOT) liu.se> wrote
| Quote: |
I hope I'm thinking right ... do you agree?
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Mon Mar 01, 2004 12:34 pm Post subject: Re: How to give access to const-interface of a base-class? |
|
|
"Jarkko Lempiainen" <jarkko (AT) videotron (DOT) ca> wrote
| Quote: | I still have to see the compiler where the solution I
proposed doesn't work
|
And I've yet to see one where it does work in the case of multiple
inheritance. And I've yet to see any serious C++ application that
doesn't use multple inheritance.
--
James Kanze GABI Software mailto:kanze (AT) gabi-soft (DOT) fr
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Jarkko Lempiainen Guest
|
Posted: Wed Mar 03, 2004 11:50 am Post subject: Re: How to give access to const-interface of a base-class? |
|
|
Many serious C++ applications don't use MI in scenarios
where it would cause problems in the proposed solution.
I think MI is quite bad argument against use of the technique
anyway, because that's something that's usually avoidable
with composition. I'm not saying MI is totally useless, but
in many places I have seen it used, it is, and only adds
unnecessary complexities to class hierarchies.
So, if you use MI as your argument to reject an useful
techique, it's your call, but I find it quite weak and unpractical.
As I have said few times before, you can detect if someone
is trying to do implicit cast and force to the use of explicit
cast instead.
Jarkko
<kanze (AT) gabi-soft (DOT) fr> wrote
| Quote: |
And I've yet to see one where it does work in the case of multiple
inheritance. And I've yet to see any serious C++ application that
doesn't use multple inheritance.
|
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|