Interface reference to local implementation
Please consider the following code:
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
struct C : public B { int go() { return 2; } };
int main()
{
B b;
B &b_ref = b;
return b_ref.go();
}
Under GCC 4.4.1 (using -O2
), the call to B::go()
gets inlined (ie., no virtual dispatch happens). That means the compiler acknowledges a_ref
indeed points to a B
type variable. A B
reference can be used to point to a C
, but the compiler is smart enough to foresee this is not the case, so it totally optimizes away the function call, inlining the function.
Great! That's an incredible optimization.
But, then, why doesn't GCC do the same in the following case?
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
struct C : public B { int go() { return 2; } };
int main()
{
B b;
A &b_ref = b;
return b_ref.go(); // B::go() is not inlined here, and a virtual dispatch is issued
}
Any ideas? What about other compilers? Is this kind of optimization common? (I'm very new into this kind of compiler insight, so I'm curious)
If the second case worked I could create some really great templates, like these:
template <typename T>
class static_ptr_container
{
public:
typedef T st_ptr_value_type;
operator T *() { return &value; }
operator const T *() const { return &value; }
T *operator ->() { return &value; }
const T *operator ->() const { return &value; }
T *get() { return &value; }
const T *get() const { return &value; }
private:
T value;
};
template <typename T>
class static_ptr
{
public:
typedef static_ptr_container<T> container_type;
typedef T st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<static_ptr_container<T>>
{
public:
typedef static_ptr_container<T> container_type;
typedef typename container_type::st_ptr_value_type st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<const T>
{
public:
typedef const static_ptr_container<T> container_type;
typedef const T st_ptr_value_type;
static_ptr() : container(NULL) {}
s开发者_高级运维tatic_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
template <typename T>
class static_ptr<const static_ptr_container<T>>
{
public:
typedef const static_ptr_container<T> container_type;
typedef typename container_type::st_ptr_value_type st_ptr_value_type;
static_ptr() : container(NULL) {}
static_ptr(container_type *c) : container(c) {}
inline operator st_ptr_value_type *() { return container->get(); }
inline st_ptr_value_type *operator ->() { return container->get(); }
private:
container_type *container;
};
These templates could be used to avoid virtual dispatch in many cases:
// without static_ptr<>
void func(B &ref);
int main()
{
B b;
func(b); // since func() can't be inlined, there is no telling I'm not
// gonna pass it a reference to a derivation of `B`
return 0;
}
// with static_ptr<>
void func(static_ptr<B> ref);
int main()
{
static_ptr_container<B> b;
func(b); // here, func() could inline operator->() from static_ptr<> and
// static_ptr_container<> and be dead-sure it's dealing with an object
// `B`; in cases func() is really *only* meant for `B`, static_ptr<>
// serves both as a compile-time restriction for that type (great!)
// AND as a big runtime optimization if func() uses `B`'s
// virtual methods a lot -- and even gets to explore inlining
// when possible
return 0;
}
Would it be practical to implement that? (and don't go on saying it's a micro-optimization because it may well be a huge optimization..)
-- edit
I just noticed the problem with static_ptr<>
has nothing to do with the problem I exposed. The pointer type is kept, but it still doesn't inline. I guess GCC just doesn't go as deep as needed to find out static_ptr_container<>::value is not a reference nor pointer. Sorry about that. But the question still remains unanswered.
-- edit
I've worked out a version of static_ptr<>
that actually works. I've changed the name a bit, also:
template <typename T>
struct static_type_container
{
// uncomment this constructor if you can't use C++0x
template <typename ... CtorArgs>
static_type_container(CtorArgs ... args)
: value(std::forward<CtorArgs>(args)...) {}
T value; // yes, it's that stupid.
};
struct A
{
virtual ~A() {}
virtual int go() = 0;
};
struct B : public A { int go() { return 1; } };
inline int func(static_type_container<Derived> *ptr)
{
return ptr->value.go(); // B::go() gets inlined here, since
// static_type_container<Derived>::value
// is known to be always of type Derived
}
int main()
{
static_type_container<Derived> d;
return func(&d); // func() also gets inlined, resulting in main()
// that simply returns 1, as if it was a constant
}
The only weakness is that the user has to access ptr->value
to get the actual object. Overloading operator ->()
doesn't work in GCC. Any method returning a reference to the actual object, if it it's inline, breaks the optimization. What a pity..
This is not a definite answer, but I thought I might post it anyway since it might be useful to some people.
Commenter Julio Guerra pointed out a C++ idiom (they call it a "paradigm" in the papers, but I think that's a bit too much) called Static C++ Object-Oriented Programming (SCOOP). I'll be posting this to give SCOOP more visibility.
SCOOP was invented to allow C++ programmers to get the best of both the OOP and the GP worlds by making both play well together in C++. It aims primarily at scientific programming because of the performance gain it can bring and because it can be used to increase code expressivity.
SCOOP makes C++ generic types emulate seemingly all aspects of traditional object-oriented programming — statically. This means template methods get to feature, for example, the ability to be properly overloaded and (apparently) issue much more proper error messages than those usually caused by your casual template function.
It can also be used to do some funny tricks such as conditional inheritance.
What I was trying to accomplish with static_ptr<>
was precisely a type of static object-orientation. SCOOP moves that to a whole new level.
For those interested, I've found two papers talking about this: A Static C++ Object-Oriented Programming (SCOOP) Paradigm Mixing Benefits of Traditional OOP and Generic Programming and Semantics-Driven Genericity: A Sequel to the Static C++ Object-Oriented Programming Paradigm (SCOOP 2).
This idiom is not without its own faults, though: it's one of those uncommon things that should be your last resort since people will most likely have a tough time figuring what you did, etc. Your code will also get more verbose and you're likely to find yourself unable to do things you thought would be possible.
I'm sure it's still useful under some circumstances, though, not to mention real fun.
Happy template hacking.
精彩评论