Constructor-as-function-call idiomblogtesthttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/blogtestikiwiki2010-08-30T18:42:25Zyou've got the right idea alreadyhttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_2/Sean2010-07-08T00:03:16Z2010-07-07T22:39:50Z
<p>You should really not put any kind of processing in a constructor. There are a number of reasons for this, ranging from "good taste" to readability to compiler optimization behavior to copy behavior to exception safety. From a pure style perspective, the basic idea is that a constructor should do nothing more than fill in the basic necessary member values for the object to be in a known good and usable state. That means initializing member variables, possibly doing some allocations for buffers or member objects, and nothing more. If the object has no members and no parent objects with non-default constructors then you should not write a constructor for your object. Likewise, a destructor should only clean up the object's owned resources, such as file descriptors or allocated memory/objects.</p>
<p>The way a lot of standard library calls work is to wrap it in a function, just like you do for the second case, which is probably the easiest and clearest way to do things. It's not considered to be in bad taste by any means. Some of the Java-school people think that using free-standing functions is bad form, but honestly, let them take their over-priced and low-quality limited-OOP-centric education and shove it. <img src="http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/../../smileys/smile4.png" alt=";)" /> C++ is explicitly multi-paradigm specifically because real code in the real world on interesting applications often benefits from a mix of OOP, procedural, functional, and declarative programming in different parts of the codebase. If you're trying to write code that is procedural in nature then there's little reason not to write a procedural-style function for that code, even if that function uses an object internally as an implementation detail.</p>
<p>That's actually probably the best way to think about things. Your code is only interested in performing some transformation on the tree and getting a scalar result. That operation is most naturally written as a plain ol' (recursive) function. The fact that you're using a C++ class for its vtable and virtual member functions is an implementation detail and not the most abstract or logical API for that operation. It actually makes more sense to write it as a function with the visitor class being an internal thing than to try to expose the class in any way.</p>
<p>(So far as credentials, I've been using C++ for around 17 years, have taught it at the high school level, have worked on C++ compilers and language tools, and use it daily for professional, academic, and hobby programming.)</p>
comment 1http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_1/Anonymous2010-07-08T00:03:16Z2010-07-08T00:02:31Z
I'd suggest not using the constructor. Instead, make a function which constructs an object of that type, runs it, and returns it. Advantage: if you have more than one type of "run" you don't have to pick one.
comment 3http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_3/Greg2010-07-08T00:05:41Z2010-07-08T00:04:47Z
Now that I've spent time using ruby I wouldn't bat an eyelash at doing that kind of thing. Before I probably would have written a wrapper. Your calling method also avoids problems with passing temporary visitors.
would not do ithttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_4/fscan2010-07-08T22:59:16Z2010-07-08T22:12:39Z
<p>what happens if you try inherit from one of your visitors? i don't know for sure, but i think, as the code is then called in the base constructor, it runs before the constructor of the inherited class.
also, i think i read somewhere that the stack in the constructor does not get cleaned up (eg objects deconstructed) if an exception is thrown.</p>
RE: would not do ithttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_5/IanRomanick2010-07-08T23:01:45Z2010-07-08T23:01:43Z
<p>That's a good point. The visitors that we have right now only derive from the base <code>ir_visitor</code> class, but it's possible that we could have a deeper hierarchy in the future (though it seems unlikely).</p>
<p>Okay... Sean and fscan have convinced me. Hurray for blogging.</p>
Yeahhttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_6/Alex Besogonov2010-07-09T16:21:21Z2010-07-08T23:23:37Z
<p>I'm watching C++ glsl2 development branch.</p>
<p>A couple of comments:</p>
<p>1) Why do you use talloc? IR code seems to be a poster child for refcounted structures, and in C++ they're easy.</p>
<p>2) Manual downcast functions can be replaced by dynamic_cast, but I guess you want to avoid RTTI for now.</p>
<p>3) 'Clone' methods beg to be rewritten using copy constructors.</p>
<p>4) Long initializer lists:</p>
<p>glsl_type::glsl_type(GLenum gl_type,
unsigned base_type, unsigned vector_elements,
unsigned matrix_columns, const char *name) :
gl_type(gl_type),
base_type(base_type),
sampler_dimensionality(0), sampler_shadow(0), sampler_array(0),
sampler_type(0),
vector_elements(vector_elements), matrix_columns(matrix_columns),
name(name),
length(0)</p>
<p>You can remove all zero initializers and instead write classes like this: http://www.boost.org/doc/libs/1_41_0/libs/utility/value_init.htm <img src="http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/../../smileys/smile.png" alt=":)" /> And remove explicit bitfields using Boost.Bitfield <img src="http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/../../smileys/smile.png" alt=":)" /> OK, ok. I'm kidding - Boost is too hardcore for most of us.</p>
<p>5) I would have used Boost.Serialization library instead of homegrown IO.</p>
<p>As a hardcore C++ developer I can say, that your code is quite clean and easy to read. But it can be written in about 1.5-2 less lines of code if advanced C++ techniques are used.</p>
<p>PS: I really love structure ir_program <img src="http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/../../smileys/smile.png" alt=":)" /></p>
RE: Yeahhttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_7/IanRomanick2010-07-09T16:32:37Z2010-07-09T16:32:26Z
<ol>
<li><p>Using <a href="http://talloc.samba.org/talloc/doc/html/index.html">talloc</a> means that we don't have to do reference counting. By not having to do it by hand we free ourselves for a large category of bugs.</p></li>
<li><p>RTTI, templates, and a couple other things are on the "do not touch" list.</p></li>
<li><p>Not at all true. To use a copy constructor you have to know the (derived) type of the thing you're creating. With a <code>clone</code> method, you don't. There are a lot of places where we have an <code>ir_rvalue</code> pointer, but we have no idea what the actual class is. Just doing <code>some_rvalue->clone()</code> just works.</p></li>
<li><p>Probably true about the initializers.</p></li>
<li><p>We're not using homegrown IO. We're using the <code>printf</code>-like routines provided by talloc. We have to keep the compiler logs in a big buffer so that they can be provided to applications via <code>glGetInfoLog</code>.</p></li>
</ol>
<p>And thanks for the reminder about <code>ir_program</code>. That was a place holder that was supposed to be removed before we brought our code into the Mesa tree. Oops...</p>
operator overloadinghttp://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_8/Adrien G.2010-08-28T08:55:40Z2010-08-28T08:55:36Z
<p>Why not overload operator()? Doing so should enable</p>
<p>ir_visitor_foobar v;
v(instructions);</p>
RE: Yeah http://www.paranormal-entertainment.com/idr/blog/posts/2010-07-07T19:45:03Z-Constructor-as-function-call_idiom/comment_9/Alex Besogonov2010-08-30T18:42:25Z2010-08-30T18:42:23Z
<blockquote>
<p>Using talloc means that we don't have to do reference counting. By not having to do it by hand we free ourselves for a large category of bugs.</p>
</blockquote>
<p>Why not use smart pointers for that? I have recently tried to benchmark non-threadsafe refcounted smartpointers and talloc, it seems that refcounting is way faster (if a decent malloc/free implementation is provided).</p>
<p>I'm tempted to try to rewrite a part of GLSL compiler to use refcounts and see what happens. I'm also tinkering with OCaml GLSL optimizer, results are interesting so far.</p>
<blockquote>
<p>We're not using homegrown IO. We're using the printf-like routines provided by talloc. We have to keep the compiler logs in a big buffer so that they can be provided to applications via glGetInfoLog.</p>
</blockquote>
<p>Well, that's 'homegrown' for me...</p>