There are several problems with reflection in C++.
It's a lot of work to add, and the C++ committee is fairly conservative, and don't spend time on radical new features unless they're sure it'll pay off. (A suggestion for adding a module system similar to .NET assemblies has been made, and while I think there's general consensus that it'd be nice to have, it's not their top priority at the moment, and has been pushed back until well after C++0x. The motivation for this feature is to get rid of the #include
system, but it would also enable at least some metadata).
You don't pay for what you don't
use. That's one of the must basic
design philosophies underlying C++.
Why should my code carry around
metadata if I may never need it?
Moreover, the addition of metadata
may inhibit the compiler from
optimizing. Why should I pay that
cost in my code if I may never need
that metadata?
Which leads us to another big point:
C++ makes very few guarantees
about the compiled code. The
compiler is allowed to do pretty
much anything it likes, as long as
the resulting functionality is what
is expected. For example, your
classes aren't required to actually
be there. The compiler can optimize them away, inline
everything they do, and it
frequently does just that, because
even simple template code tends to
create quite a few template
instantiations. The C++ standard
library relies on this aggressive
optimization. Functors are only
performant if the overhead of
instantiating and destructing the
object can be optimized away.
operator[]
on a vector is only comparable to raw
array indexing in performance
because the entire operator can be
inlined and thus removed entirely
from the compiled code. C# and Java
make a lot of guarantees about the
output of the compiler. If I define
a class in C#, then that class will
exist in the resulting assembly.
Even if I never use it. Even if all
calls to its member functions could
be inlined. The class has to be
there, so that reflection can find
it. Part of this is alleviated by C#
compiling to bytecode, which means
that the JIT compiler can remove
class definitions and inline
functions if it likes, even if the
initial C# compiler can't. In C++,
you only have one compiler, and it
has to output efficient code. If you
were allowed to inspect the metadata
of a C++ executable, you'd expect to
see every class it defined, which
means that the compiler would have
to preserve all the defined classes,
even if they're not necessary.
And then there are templates.
Templates in C++ are nothing like
generics in other languages. Every
template instantiation creates a
new type. std::vector<int>
is a completely separate class from
std::vector<float>
. That adds up to
a lot of different types in a entire
program. What should our reflection
see? The template std::vector
? But
how can it, since that's a
source-code construct, which has no
meaning at runtime? It'd have to see
the separate classes
std::vector<int>
and
std::vector<float>
. And
std::vector<int>::iterator
and
std::vector<float>::iterator
, same
for const_iterator
and so on. And
once you step into template
metaprogramming, you quickly end up
instantiating hundreds of templates,
all of which get inlined and removed
again by the compiler. They have no
meaning, except as part of a
compile-time metaprogram. Should all
these hundreds of classes be visible
to reflection? They'd have to,
because otherwise our reflection
would be useless, if it doesn't even guarantee that the classes I defined will actually be there. And a side problem is that the template class doesn't exist until it is instantiated. Imagine a program which uses std::vector<int>
. Should our reflection system be able to see std::vector<int>::iterator
? On one hand, you'd certainly expect so. It's an important class, and it's defined in terms of std::vector<int>
, which does exist in the metadata. On the other hand, if the program never actually uses this iterator class template, its type will never have been instantiated, and so the compiler won't have generated the class in the first place. And it's too late to create it at runtime, since it requires access to the source code.
- And finally, reflection isn't quite
as vital in C++ as it is in C#. The
reason is again, template
metaprogramming. It can't solve
everything, but for many cases where
you'd otherwise resort to
reflection, it's possible to write a
metaprogram which does the same
thing at compile-time.
boost::type_traits
is a simple
example. You want to know about type
T
? Check its type_traits
. In C#,
you'd have to fish around after its
type using reflection. Reflection
would still be useful for some
things (the main use I can see,
which metaprogramming can't easily
replace, is for autogenerated
serialization code), but it would
carry some significant costs for
C++, and it's just not necessary as often as it is in other languages.
Edit:
In response to comments:
cdleary:
Yes, debug symbols do something similar, in that they store metadata about the types used in the executable. But they also suffer from the problems I described. If you've ever tried debugging a release build, you'll know what I mean. There are large logical gaps where you created a class in the source code, which has gotten inlined away in the final code. If you were to use reflection for anything useful, you'd need it to be more reliable and consistent. As it is, types would be vanishing and disappearing almost every time you compile. You change a tiny little detail, and the compiler decides to change which types get inlined and which ones don't, as a response. How do you extract anything useful from that, when you're not even guaranteed that the most relevant types will be represented in your metadata? The type you were looking for may have been there in the last build, but now it's gone. And tomorrow, someone will check in a small innocent change to a small innocent function, which makes the type just big enough that it won't get completely inlined, so it'll be back again. That's still useful for debug symbols, but not much more than that. I'd hate trying to generate serialization code for a class under those terms.
Evan Teran: Of course these issues could be resolved. But that falls back to my point #1. It'd take a lot of work, and the C++ committee has plenty of things they feel is more important. Is the benefit of getting some limited reflection (and it would be limited) in C++ really big enough to justify focusing on that at the expense of other features? Is there really a huge benefit in adding features the core language which can already (mostly) be done through libraries and preprocessors like QT's? Perhaps, but the need is a lot less urgent than if such libraries didn't exist.
For your specific suggestions though, I believe disallowing it on templates would make it completely useless. You'd be unable to use reflection on the standard library, for example. What kind of reflection wouldn't let you see a std::vector
? Templates are a huge part of C++. A feature that doesn't work on templates is basically useless.
But you're right, some form of reflection could be implemented. But it'd be a major change in the language. As it is now, types are exclusively a compile-time construct. They exist for the benefit of the compiler, and nothing else. Once the code has been compiled, there are no classes. If you stretch yourself, you could argue that functions still exist, but really, all there is is a bunch of jump assembler instructions, and a lot of stack push/pop's. There's not much to go on, when adding such metadata.
But like I said, there is a proposal for changes to the compilation model, adding self-contained modules, storing metadata for select types, allowing other modules to reference them without having to mess with #include
s. That's a good start, and to be honest, I'm surprised the standard committee didn't just throw the proposal out for being too big a change. So perhaps in 5-10 years? :)
The reason Herb said what he said is because of cases like this.
Let's say I have function A
which calls function B
, which calls function C
. And A
passes a string through B
and into C
. A
does not know or care about C
; all A
knows about is B
. That is, C
is an implementation detail of B
.
Let's say that A is defined as follows:
void A()
{
B("value");
}
If B and C take the string by const&
, then it looks something like this:
void B(const std::string &str)
{
C(str);
}
void C(const std::string &str)
{
//Do something with `str`. Does not store it.
}
All well and good. You're just passing pointers around, no copying, no moving, everyone's happy. C
takes a const&
because it doesn't store the string. It simply uses it.
Now, I want to make one simple change: C
needs to store the string somewhere.
void C(const std::string &str)
{
//Do something with `str`.
m_str = str;
}
Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO)). C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? And A
passes a temporary; there's no reason why C
should have to copy the data. It should just abscond with what was given to it.
Except it can't. Because it takes a const&
.
If I change C
to take its parameter by value, that just causes B
to do the copy into that parameter; I gain nothing.
So if I had just passed str
by value through all of the functions, relying on std::move
to shuffle the data around, we wouldn't have this problem. If someone wants to hold on to it, they can. If they don't, oh well.
Is it more expensive? Yes; moving into a value is more expensive than using references. Is it less expensive than the copy? Not for small strings with SSO. Is it worth doing?
It depends on your use case. How much do you hate memory allocations?
Best Answer
These Tables are used for finding out how to tesselate the surface:
The first table gives you the necessary edges to interpolate. The second table gives you the way you have to tesselate, meaning, which triangles you have to make inside the cube.
A little example:
let's assume, vertex one and 2 are below the iso level, the cubeindex should be 3.
The whole intersection should look like a wedge.
If you think about it, you have to interpolate values on the edges: 0 and 9 , and 2 and 10. If you enter this into a bitfield, each bit corresponding to "is edge intersected?" you would end up with something like this:
wouldn't you?
Which is exactly the value from edgeTable[3] in binary ;) 0x30A = 1100001010
Now you can write a function that linearly interpolates the points on those edges to fit your isolevel. These points will become your surface inside this cell.
But how to tesselate this cell/surface?
if you look into triTable[3] a smile should creep over your face :)
Addit after statement of residual puzzlement in comment: ;-)
What Marching Cubes does: Imagine you have a dark room with one point light source in it. It is the center of a volumetric light intensity field of scalar intensity values. You can go to point (x,y,z) and measure the intensity there, e.g. 3 candela.
You now want to render a surface through all points that have a certain light intensity. You can Imagine that this Isosurface would look like a sphere around the point light source. That is what we hope that Marching cubes will provide us with.
Now running through all points in the room and marking every point as a vertex that has roughly the iso value, will be algorithmically very complex and would result in a hughe number of vertices. Which we would then have to tesselate somehow.
So: First Marching cubes disects the whole volume into cubes of equal size. If the underlying data has some kind of underlying discreteness, multiples of that are used. I will not go into the other case, since that is rare. For instance we put a grid with the density of 1mm into a 2mx5mx5m room
We use cubes of 5mmx5mmx5mm. Running through them should be much cheaper.
You can imagine now, that the edges of some of the cubes intersect the isosurface. These are the interesting ones. This code identifies them:
if cubeindex stays zero, this particular cube is not intersected by the isosurface. If cubeindex is > 0 you now know that the isosurface goes through this cube and you want to render the piece of the isosurface that is inside it.
Please picture this in your mind. See http://en.wikipedia.org/wiki/Marching_cubes for examples of intersected cubes.
The vertices that you could get easily are those on the edges of the cube. Just interpolate linearly between 2 corner points to find the position of the isovalue and put a vertex there. But which edges are intersected??? That is the information in edgeTable[cubeindex]. That is the big piece of code with all the ifs, that stores the interpolated points as vertices in an array of xyz points: vertlist[]. This piece reads as follows:
You now have an array full of vertices, but how to connect them to triangles? That's an info that tritable provides.
The rest is pretty much what I explained above.
Well should there still be problems, please be specific about the piece of code that gives you trouble.