• Advertisement
Sign in to follow this  

Code Review

This topic is 978 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I recently interviewed with a CAD-based studio where I live for a C++/Renderer position. The role was to work on their internal 3D rendering code that's written in DX/OpenGL. They asked for a code sample of one of my personal projects. I sent them one of my projects. It's a basic, open source, texture packer application using Qt to provide the UI. It's not much, but at least it's pretty complete. The CEO got back to me about 10 days later, and said I was too junior for their company at this time. They're looking to fill a senior position first, then they'd consider bringing me on to get mentored.

 

I was only a little disappointed at first, but more curious than anything. I've been programming since middle school, but I'm only 2 years into programming professionally full-time. I've worked in 2 smaller/startup companies in this time. None of my work has been C++ or lower-level rendering-oriented, so it didn't surprise me when they turned me down. I know I'm not the best C++ programmer out there. I'd like to have some one with better knowledge of C++ to point me in the right directly as far as decent C++ practices go. I don't know if there are any good resources for this online, so I wanted to ask for one in this thread, if that's allowed. I'd really appreciate it if anyone could give me feedback. It can be downloaded here. If anything, you'd get a texture packer with source out of it.

Share this post


Link to post
Share on other sites
Advertisement

I'd venture to guess that your lack of experience (two years) contributed fairly significantly to your being considered "too junior," but the demo you submitted probably didn't help. Not necessarily because of the code itself (which I have not even looked at yet), but simply because of what it is: texture packing is not that advanced a topic unless you're actually demonstrating some new, novel mechanism of solving the bin-packing problem efficiently (which would still probably be mostly of note to somebody in academia). It's also not hugely relevant to low-level rendering.

 

I'll take a look at the code later on and see if I can give you some feedback.

Share this post


Link to post
Share on other sites

Quick glance at the code, you pass parameters by value specially std::string, you do not use const anywhere including marking methods which do not change state of an object. Those two things would be give me the impression that you are a junior.

Share this post


Link to post
Share on other sites

@DoctorGlow

(Offtopic Question probably)

std::string should be passed by reference? Or should you even pass a pointer to a string instead of the string itself? I always considered a string as a primitive type so I did neither...

Share this post


Link to post
Share on other sites

Sorry if this is tl;dr. It took me 3 hours to write up a response. Took it to heart though.

 

I'd venture to guess that your lack of experience (two years) contributed fairly significantly to your being considered "too junior," but the demo you submitted probably didn't help.

 

This is what I thought too. The sprite packer and signed-distance font generator were the only two presentable projects I really had. All of my ambitious projects break over time due to underlying code changes from a shared code base. I'm fixing that by providing version control --something I never really used until 2 years ago.

 

 

 

Quick glance at the code, you pass parameters by value specially std::string, you do not use const anywhere including marking methods which do not change state of an object. Those two things would be give me the impression that you are a junior.

 

 

AND

 

 

 

std::string are objects (class) and can be expensive, specialy if it needs to copy data, so I "always" pass by const ref.

 

Another big concern of mine. I know that passing things by value's kills performance, but I haven't wrapped my head around how to use references, const references, and const getters effectively. I was going to make this a future priority, but since that half the replies are about how to properly pass data into functions, it sounds like it's a fundamental I need to work on right now.

 

I can see how passing by const reference acts as a security concern, but does it also increase performance as well making it read-only? The same goes for plain references: security in the sense that they're restricted pointer. They can't be re-assigned after assignment, no direct pointer arithmetic, can't ever be NULL, and can't take the address of references. These points were brought up in this Stack Overflow thread, but I'm not sure what the 3rd point means. Also, is it proper to return const references/pointers from methods? I've tried wrapping my head around this as well.

 

There are a lot of things I'll write, but I'll write them in weird ways because I'm unsure of the proper way for it to be written in certain situations. Josh brings up some of them.

 

@Josh Petrie: Thank you for taking the time during your lunch break to go over this. There are plenty of gems here for a junior like myself, and I did find your comments very informative. Here are my responses:

 

BinPacker.h

  • I wrote my header guards like this since that's how I saw them early-on in C++ tutorials. As time went on, I adopted this convention. Then again, I've seen plenty of examples online that don't use underscores like this. It sounds like a potential issue for some compilers, so I'll remove both the preceeding and trailing underscores.

  • Regarding BinRect and inheritance abuse: I agree with you. I thought about using composition here as I think simple types like VectorX, MatrixX, ColorX, Rect, etc should all be final classes. I should be implementing that as well. When I wrote this code back in November, 2014, I was just getting exposed to the idea that inheritance isn't the end-all solution in C++. I tried staying away from it, which is why BinPacker is generic, but again, inheritance prevailed as a design choice in every class contained in the BinPacker module. Providing a HAS A Rect relationship with BinRect and BinNode should help fix some points that you bring up later.

  • 'rotated' is supposed to be treated as a bool value. I use uint8_t because I wanted it strictly typed for POD purposes so it's more portable when exporting to a file. I try to stay away from bool when working with POD due varying to compiler implementation. bool can vary between platforms/implementations, so I wanted to strictly type something to an unsigned 8-bit value. Also, it doesn't seem like I can XOR bools on some compiler. I use XOR to toggle rotation state of my BinRects. If using a bool, I could toggle simply by myBoolVar = !myBoolVar. Checking > 0 is true, but again, I should just check if(rotated == true) { }. Would a bool still be a better way to go? It'd certainly make it more readable.

  • I always thought I had to check if my pointer was NULL before deleting it. Since this isn't the case, I'll remove the checks.

  • You bring up a good point about CompareBinRects() as a template is unnecessary. I can't remember why I even did that, but again, making it a template function doesn't make sense. I moved it into BinPacker as a private method.

  • BinPacker is a template because it's meant to take a subclass BinRect. BinRect::CanPack() was a hasty addition to the original design because I realized that some rects included in the rects vector shouldn't be packed due to some sort of invalidation in some implementations of this class. This is obviously poor design. Rect validation should be done before adding them to the packer.

  • I agree with ForceSquare(): it sounds like an action. I was uneasy when I wrote it. It does set a state that'll be applied when Commit() gets called, but again, doesn't perform any action. This should be renamed something better, such as ForceSquareFlag() maybe?

  • I agree with using "unsigned" when negative values should never be used. I only kept it signed as I thought this was a common convention, and also performed better. I remember reading about signed vs unsigned ints, and performance impact a while ago when I did PSP development. I'm probably wrong. I'll use "unsigned" in places where values should be unsigned, such as looking up an array item by index. Everything should be [0, max value), but don't some compilers yield a warning if I use an unsigned datatype instead of a signed int?

  • I've gone back in forth between throwing an error, and bounding indices that are out-of-bounds. I'll throw an error instead. I'm starting to use assert() for rare situations like this. I stay away from exception handling in commonly-used code as it can be slow. Does this sound practical?

  • I agree with logging in utility code: it shouldn't be used, but still should be reported. I'm a fan of error codes, but there are caveats to that such as providing a bunch of different defines. I'd like to use C++11 enum classes as error codes here, but I'm not sure if that's good design simply because I haven't seen that yet. I'm sure exceptions have their purpose (I use them in C# in cases where I'd typically use assert() in C++). What is your take on exception handling?

  • I agree, NULL rects shouldn't be allows, and it'd be a much better practice to use a vector of objects instead a vector of pointers in this case. The only reason I'm doing this is because BinRect was originally intended to be subclassed. In reading your feedback, inheritance doesn't sound like a solid design. If I were to store objects instead of pointers in my vector, how should I match up those rects with my images or glyphs? The rects are re-ordered by size to make the packer work. I thought of providing a separate class to store my sprite info that gets "packed" by storing a pointer to the corresponding BinRect that actually gets packed, but if I'm pushing objects into a vector that could potentially resize. This could move my objects around in memory; invalidating my sprite info objects' pointers. Should an STL list of objects be a better solution? I try to stick with vectors as much as possible for cache reasons.

  • I moved that function into BinPacker as a method since it's only practical purpose is specific to that class.
  • Good call with the BinNode root. It'll become part of the stack.
  • Again, right about GetNumPackedRects(): it literally does nothing but return zero. I forget what that was even supposed to do. It's not even used in the entire applications. It's deleted.

 

BinPacker.cpp

  • Should have seen that one coming. I'll go with error codes for BinPacker::Divide(). It currently only returns a bool value, but I could convert that into error codes to get more specific on what went wrong.

Matrix.h:

  • I've recently started to get away from being too vague about naming conventions, specifically functions/methods. I'd try to overload methods as much as possible, but I've seen cases where I've gone overboard in other projects. By naming these methods.
  • I see your point, and I could provide documentation on this. This class uses its data in a column-major format as every graphics API I've seen uses the column-major interpretation (DirectX, OpenGL, the PSP's GU). I've considered this a given, but you're right: it wouldn't hurt to specify somewhere.
  • NOTE: Just like all other complex math types I have in classes, they all have operator overloads. There are many opportunities for const reference parameters.

SpritePacker.h

  • I was wondering if SpritePacker would violate SRP. In fact, I'm not very confident in my philosophy of the single responsibility pattern to derived classes without violating SRP. The CanPack() virtual method will be removed, as stated above. I initially thought the same thing: the packer should only pack rects, not sprites. I abandoned that philosophy because I wasn't sure how to associate sprites with packed rects if my BinRects are moved around in memory as a vector (might have to go with a list on this one, unfortunately). The idea was that SpriteBinRects could be pushed into the SpritePacker even when a texture wasn't working. Of course, if a texture were to fail, the SpriteBinRect shouldn't be instantiated in the first place.
  • I thought the same thing about the ternary state: it returns a boolean value. The only reason I explicitly specify true : false is because I think I ran into a compiler error when assigning a bool to an expression evaluation.
  • Again, you're right, passing by value is largely unneeded. I'm cleaning this up as I get the time to do so. Hopefully I'll have an amended version of this code completed by the end of the week.
  • Yeah, SpritePacker will suffer the same issues from BinPacker.

SpritePacker.cpp:

  • INPLACESWAP and SwapRedBlue32 aren't my own code. I took it from FreeImage, and put it in my code-base since these functions aren't always available in every version of FreeImage's source code. I need it to swap Blue/Red components since FreeImage has the nerve to store those components in a BGR format. Some people... lol jk, FreeImage has been VERY useful. biggrin.png
  • I've wondered about where to put the vertex/shader source code. I put it in InitCommon() because it doesn't need to exist past setting up a few stock shaders. After that, the data isn't needed anymore. I let it release from the stack after InitCommon() reaches the end of its scope. Do I still need to move it somewhere as static common data? It'd only eat up a few KB of memory if I did that.

Types.h

  • I setup the assignment operator overloads properly where the parameter takes a const reference, and returns a reference. I've seen this in others' code (tinyxml, FreeImage, STL, Box2D, etc). Again, I've stayed away from references and const references out of lack of understanding. As stated above, it sounds like this should be a top priority.

 

 

 


Most of the above is not too bad, the biggest problems I would say are the way you use run- and compile-time polymorphism, the way you deal with ownership, and the apparent consideration you put in to the shape and surface area of your APIs

I still struggle with all of these. I can nail a lot of the simple stuff, but I don't most of the time. I've been programming in C++ as an obsessive hobby since I was in middle school, about 11 years ago. That said, I still do consider myself novice. I've started to notice that I need to get feedback from other programmers to grow, otherwise I'll just be stuck in my old ways. I just gotta face that I'm not John Carmack --I'm just a regular guy lol. I'll to ask more questions, and apply the advice in smaller test cases to reinforce what I'm being taught.

 

I can't thank you all enough for the feedback.

Edited by Vincent_M

Share this post


Link to post
Share on other sites

I'd like to have some one with better knowledge of C++ to point me in the right directly as far as decent C++ practices go.

I think it's a pretty good idea to read Scott Meyers's Effective C++.

It's easy to read, there are short and easily understandable tips which can help you improve your code.

Share this post


Link to post
Share on other sites

Another big concern of mine. I know that passing things by value's kills performance, but I haven't wrapped my head around how to use references, const references, and const getters effectively. I was going to make this a future priority, but since that half the replies are about how to properly pass data into functions, it sounds like it's a fundamental I need to work on right now.



I can see how passing by const reference acts as a security concern, but does it also increase performance as well making it read-only? The same goes for plain references: security in the sense that they're restricted pointer. They can't be re-assigned after assignment, no direct pointer arithmetic, can't ever be NULL, and can't take the address of references. These points were brought up in this Stack Overflow thread, but I'm not sure what the 3rd point means. Also, is it proper to return const references/pointers from methods? I've tried wrapping my head around this as well.

 

Wrapping your head around when to  use (const) references is really easy.

 

For primitive values and small structs (something like 1-3 four-byte values), you pass by value, unless you want to modify the variable you are passing. In this case you pass by reference, unless the parameter is optional, in this case you pass by pointer.

For larger classes/structs, you generally always pass by at least const reference. Unless you want to modify the class of course, then you pass by reference. Unless again the value is optional, in which case you pass by pointer.

 

Performance in this regards will be increased when you pass stuff like std::vector, std::string which can take a large amount of time to copy. I wouldn't say they exactly increase security, since you can do stuff like this unfortunately:

void takeString(std::string& string)
{
    string.clear();
}

std::string* pString = nullptr;

takeString(*pString); // crash inside the function as if you were passing nullptr

but they document intent by telling the user of your function "the value of this parameter is required", while using only pointers technically make it unclear whether or not they have to take a value or can be nullptr.

Edited by Juliean

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement