Interface link to local implementation

Pay attention to 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(); } 

In GCC 4.4.1 (using -O2 ), the call to B::go() becomes nested (that is, virtual sending does not occur). This means that the compiler confirms that a_ref does indeed point to a variable of type B Reference B can be used to point to C , but the compiler is smart enough to anticipate that it is not, so it fully optimizes the function call by inserting the function.

Excellent! This is an incredible optimization.

But why does the GCC not 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 optimization common? (I'm very new to this prototype compiler, so I'm curious)

If the second case worked, I could create some really great templates, for example:

 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) {} 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 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 patterns can be used to avoid virtual sending 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 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 this? (and don’t keep talking about micro-optimization, because it can be a huge optimization.)

- edit

I just noticed that the problem with static_ptr<> has nothing to do with the problem that I uncovered. The type of pointer is preserved, but it is still not inlined. I think GCC is simply not as deep as necessary to find out that static_ptr_container <> :: value is not a reference or pointer. Sorry about that. But the question is still unanswered.

- edit

I developed a version of static_ptr<> that really works. I changed the name a little:

 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 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 must access ptr->value in order to get the actual object. Overloading operator ->() does not work in GCC. Any method that returns a reference to the actual object, if it is built-in, breaks the optimization. What a pity..

+4
source share
1 answer

This is not a definite answer, but I thought I could post it anyway, as it might be useful to some people.

Julio Guerra's comment pointed to the C ++ idiom (they call it a β€œparadigm” in the docs, but I think a little too much) is called Static C ++ Object-Oriented Programming (SCOOP). I will post this to give SCOOP more visibility.

SCOOP was invented to allow C ++ programmers to make the best use of both OOP and global worlds, forcing both to play well together in C ++. It is primarily aimed at scientific programming because of the increase in productivity that it can bring, and because it can be used to increase the expression of code.

SCOOP makes common C ++ types emulate, apparently, all aspects of traditional object-oriented programming - statically. This means that the template methods allow you to use, for example, the ability to properly overload and (apparently) produce much more correct error messages than those that are usually called by your random template function.

It can also be used to perform some fun tricks, such as conditional inheritance.

What I'm trying to do with static_ptr<> was just a type of static object orientation. SCOOP takes this to a whole new level.

For those who are interested, I found two documents that talk about this: Static C ++ - Object Oriented Programming (SCOOP) Mixing the Paradigm with Traditional OOP and General Programming and Semantics-Based Generalization: Continuing the Theoretical Paradigm of Object-Oriented Programming Static C ++ (SCOOP 2) .

This idiom is not without its own shortcomings: it is one of those unusual things that should be your last resort, since it will most likely be difficult for people to determine what you have done, etc. Your code will also be more verbose and you will probably find that you cannot do what you think is possible.

I am sure that it is still useful in some circumstances, although not to mention real fun.

Happy hacking templates.

+2
source

All Articles