I find myself using policies a lot in my code and usually I'm very happy with that. But from time to time I find myself confronted with using that pattern in situations where the Policies are selected and runtime and I have developed habbits to work around such situations. Usually I start with something like that:
class DrawArrays {
protected:
void sendDraw() const;
};
class DrawElements {
public:
void setIndices( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
protected:
void sendDra开发者_Go百科w() const;
};
template<class Policy>
class Vertices : public Policy {
using Policy::sendDraw();
public:
void render() const;
};
When the policy is picked at runtime I have different choices of working around the situation.
Different code paths:
if(drawElements) {
Vertices<DrawElements> vertices;
} else {
Vertices<DrawArrays> vertices;
}
Inheritance and virtual calls:
class PureVertices {
public:
void render()=0;
};
template<class Policy>
class Vertices : public PureVertices, public Policy {
//..
};
Both solutions feel wrong to me. The first creates an umaintainable mess and the second introduces the overhead of virtual calls that I tried to avoid by using policies in the first place.
Am I missing the proper solutions or do I use the wrong pattern to solve the problem?
Use the second version. Virtual calls are more expensive than static calls because they require an additional pointer lookup, but if "sendDraw" does any real drawing, you won't notice the difference. If you really have a performance problem later, use a profiler to find out where the problem is and fix it. In the (extremely unlikely) case that the virtual method call is actually a performance problem, you could try optimizing it using policies. Until then, write code that's most maintainable so you have development time left to optimize later.
Remeber: Premature optimization is the root of all evil!
In general, if you need behavior to vary at runtime you are going to have to pay some overhead cost for that, whether it be a switch/if statement or a virtual call. The question is how much runtime variance you need. If you're very confident you will only ever have a small number of types, then a switch statement may really be appropriate. Virtual calls give more flexibility for extending in the future, but you don't necessarily need that flexibility; it depends on the problem. That said, there's still a lot of of ways to implement your 'switch statement' or your 'virtual call'. Instead of a switch/if you could use the Visitor Pattern (more maintainable), and instead of virtual calls you could use function pointers (when it doesn't make sense for the class itself to specify the behavior that is invoked at runtime). Also, although I don't agree with everything the author says (I think he artificially makes his idea and OOP mutually exclusive) you might be interested in Data Oriented Programming, especially if you're working on rendering as your class names suggest.
Why do you oppose virtual calls? Is the overhead really considerable for you? I think the code becomes more readable when you express what you want to do by writing an interface and different implementations instead of some unreadable templates.
Anyway, why do you inherit Vertices
from Policy
class? You already have it as a template argument. Looks like composition is more appropriate here. If you use inheritance, you can have just one non-template class Vertices
and change its behaviour by passing different Policy
objects - this is Strategy pattern.
class Policy {
public:
void sendDraw() const =0;
}
class Vertices {
public:
Vertices(Policy * policy) :
: policy(policy)
{
}
void render() {
// Do something with policy->sendDraw();
}
}
I don't see anything wrong with the first one - it doesn't look like an unmaintainable mess to me, although there's not enough code here to determine if there might be a better refactoring.
If you aren't putting the draw calls into a display list then the array data will have to be copied out when it's drawn. (Either the caller blocks until the GPU is done, or the driver copies it out of the app memory to somewhere safe.) So the virtual function won't be an issue. And if you ARE putting them in a display list, then the virtual function won't be an issue, because it's only being set up the once.
And in any event, PCs do virtual calls very quickly. They're not free, it's true, but if drawing (say) thousands of sets of vertices per frame then an extra virtual function call per draw is highly unlikely to break the bank. Out of all the things to think about ahead of time, avoiding uses of a virtual function in the very sorts of situation that virtual functions are designed for is probably one of the less important ones. Unnecessarily-virtual functions are worth avoiding; genuinely useful virtual functions are innocent until proven guilty...
(Drawing more vertices per call and changing shader, shader constants, vertex format and render state settings less frequently are likely to pay greater dividends.)
精彩评论