Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411283 Posts in 69325 Topics- by 58380 Members - Latest Member: bob1029

March 29, 2024, 01:35:00 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)General thread for quick questions
Pages: 1 ... 39 40 [41] 42 43 ... 69
Print
Author Topic: General thread for quick questions  (Read 132716 times)
JohnsyJohnsy
Level 0
**


View Profile
« Reply #800 on: July 30, 2016, 09:56:25 AM »

0: JS and C
1:  It sometimes feels unneeded and like boring administation, sometimes it feels very professionally and secure and proper.
2: I sometimes do it , sometimes I don't.

Quote
"The sort of testing I did above just to see if stuff works ...
I definitely cannot see the practicality of that, at least for my purposes. "

- The biggest gain I think unit testing gives me is in refactoring or rewriting large pieces of code. Imagine you changing around alot of code in a refactor, its nice to just say "make test" and see if your improvements didnt break all sorts of thing you didnt think of.
- It's also a nice way of documenting my code I think, I don't do alot of comments, but peeking into the unit tests shows me what code is supposed to do and what the outcome should be.
- It's also a nice place to put strange bugs, say you run into a bug, you make a test for it (and what the outcome ought to be) after that you are "sure" the bug is squashed.
- When working together and rewriting/changing other people's code its nice to have tests to see if you're changes doesnt mess up what they where expecting of the code.

that are some practical gains I get from unit testing, I would also like to use QuickCheck (https://en.wikipedia.org/wiki/QuickCheck) because its crazy cool since it generates huge tests itself (read about it, play with it) But i havent got around to it.

Quote
I generally have no trouble keeping track of huge projects for years on end, but bugs do inevitably crop up, and I'm wondering if this sort of attention to detail might help out preventing those. But I'm also getting the feeling that unit tests are more of a team tool, and not something I necessarily have to use as a lone developer..? It also feels less like a game development thing and more like "enterprise" stuff (whatever that means)... And some things, like graphics code, can't really be tested this way, can it?

I guess you are sort of right about this, for graphics testing you could use some screengrabbing/comparing to expected output though, and the refactoring/documenting properties still stand even though you're a lone wolf. And offcousre the bugs that crop up won't feel like whack a mole if they where being tested and secured behind unit tests.

But don't get me wrong, I prefer adding new features to testing things I did, sometimes i start in a tdd fashion, but I a m just too lazy too keep it up at all times.
Logged
oahda
Level 10
*****



View Profile
« Reply #801 on: July 30, 2016, 10:06:17 AM »

When it comes to comments I'm already documenting the whole API with Doxygen comments (like JavaDoc for C++ and some other languages) from which web documentation pages can be generated, so that one's covered.

But yeah, I think I might play around with my custom, simplified version of testing (may not necessarily be unit testing, but sort of). My macros make it just about as quick as manually printing something to the console to check for correct values anyway. Here are some tests I wrote today after refactoring my vector/colour class, adding 4D vectors and RGBA colours, rgba aliases for xyzw and swizzles for everything, to make it mirror GLSL (don't worry, there are macros and generated code involved—I'm not that much of a masochist!):



And it did catch one error: the swizzling wasn't working correctly. Found the error and fixed it. Neato. That's as far as I'll go, I think. The green success labels versus the red failure labels give nice and quick feedback.

This is the syntax for the last test, for example:

Code:
KARHU_TEST("The two should NOT match now", (colCopy != col))

Not too bad! As a bonus, this came with a general colourful overhaul of my entire logging system. Gomez

Logged

oahda
Level 10
*****



View Profile
« Reply #802 on: August 07, 2016, 12:57:56 AM »

So I know people in general seem a bit iffy about using exceptions in C++, and so I rarely if ever use them either. But I'm thinking... in this particular situation I have, which is not all too uncommon...

I avoid exposing pointers to the user. I always return references. So there might be a method along the lines of getChildByName(string). The name supplied can obviously be one that doesn't match any child.

So depending on one's structure, this could be handled in different ways. If using pointers, null could be returned. Something similar could be done if a pointer was passed as a parameter to be filled in, but a success/error state could be returned by the method...

But I'm using references, so I can't do that. So there are only really two viable options: either forcing the user to first check manually by using something like hasChildByName(string), or by throwing an exception if the child doesn't exist so that it can be caught and handled.

I'm wondering, isn't it generally slower, depending on the size of the list of children, to have to look it up twice every time, than to just use a try-catch block more or less like an if clause to deal with finding or not finding the child. Or is there some particular argument against exceptions here that would actually make it slower simply to have the program throw for whatever reason? Since everyone is so anti-exceptions in C++, I've mostly just avoided them by recommendation, and never really properly learned exactly why people hate them.

I'll head out on the webs to try and find something to read about that, but I thought I'd ask here to get some personalised replies as well.
Logged

JWki
Level 4
****


View Profile
« Reply #803 on: August 07, 2016, 01:35:41 AM »

Never ever use exceptions for control flow.
Putting performance considerations aside for one moment, exceptions are not and were never meant to be used as a means to control the execution flow of your program. Even programmers that are proponents of exception use tend to recommend against such an usage.
The point is that exceptions are meant to be used in really exceptional cases, failure cases that would result in an invalid program state of some kind if you don't, well, catch the exception. They are NOT meant to be used as some kind of return code for cases that are not very exceptional.
Your example is a classical case that's brought up on when NOT to use an exception - looking up stuff that might not be there.
Even though it is a more or less exceptional state, because you'd expect the user of your API not to look up nonexistent stuff, it's not critical enough that it would warrant use of exceptions. Such a function is a good candidate for something that might be called in a tool, for example, when searching a specific object in the scene graph by name - and in such use cases it's likely that the user misspells the name, or that the object just isn't there. Having the program go through a try catch block for such a common operation is really nothing I'd recommend.

Now, regarding performance, if your children are stored in a data structure that's fast to search by name - and I mean really fast as in it never takes a long amount of time, not as in it's O(n) or O(log n) or something - then that's a no-brainer it is definitely going to be faster to just perform two lookups. But even if it isn't, and the try catch stuff is actually faster, it's still a really smelly solution to the problem.
Such a function is actually one where I'd always argue for returning a pointer, because that's where pointers have an advantage over references, they can be null. Also, from a users perspective, I'd expect the method to just return a pointer and return nullptr if it doesn't find anything, I definitely would NOT expect it to throw an exception and I'd be rather annoyed if I had to wrap it in a try catch block every time I call it because it seems like something you call a lot.

I'm really curious on why you try to avoid pointers so much, there's nothing bad about exposing them to the user really. References are pretty much one of the most useless features of C++ imho, there's not really an advantage I see over pointers.
Logged
oahda
Level 10
*****



View Profile
« Reply #804 on: August 07, 2016, 01:54:19 AM »

Thanks for replying. c:

Yeah, I'm one of those feeling iffy about exceptions too, so I'm not very keen on using them if I can avoid it either. But I realised I never properly questioned why, so I thought I'd request some concrete information there once and for all. Shrug I'm finding some good info online too, so I'm getting a better understanding of the exact problems now.

In case you want to compare your own opinion to those of other people BTW: http://stackoverflow.com/questions/1736146/why-is-exception-handling-bad

Should also note that I'm using C++14, in case that changes anything.

I should clarify that my primary concern is probably just having the program crash on error to allow things to be fixed during development. I've heard exceptions (like throwing an std::runtime_error) make sure stuff is properly deällocated, while something like calling assert or exit might leak. Is this true or not? One hears so much.
« Last Edit: August 07, 2016, 02:05:04 AM by Prinsessa » Logged

JWki
Level 4
****


View Profile
« Reply #805 on: August 07, 2016, 02:21:55 AM »

Oh yeah I know that stackoverflow thread.

Regarding your primary concern, asserts are pretty much what I use to make sure stuff blows up early in development so I can catch bugs and such. Memory safety isn't inherently provided by using exceptions, exceptions just allow you to write your code in a way such that basically the program cleans up upon encountering an exception - that's what writing exception safe code is about and it basically means for every line of your program you have to consider if it could throw an exception and if that's the case, how to handle that. That's one of the concerns that lead to RAII, for example. Say you have some code that calls a function that might fail. If that function throws an exception, that will effectively close the scope the function was called from, calling destructors for all the stack variables in the scope, etc, and that can be used by utilizing RAII or similiar idioms to clean up memory, close system handles, yadda yadda, whereas an assert will just crash your program without doing that for you. Also, asserts are not even present in release code (depending on how your asserts are implemented). Exceptions are, and if there's something you want to assert in release code, exceptions do that for you.
Now that may sound attractive, but you have to consider that it's not actually as useful as it sounds. Firstly, release code should not be able to enter a state that would fire an assert - that's what those are for in the first place, to iron out everything that leads to such a program state.
Also, on a modern operating system, cleaning up every bit of memory you allocated doesn't even make much sense - upon program exit, the memory will be gone for good anyways, so there's no point in freeing every last byte you allocated. External resources like mutexes, file handles, etc, are a different story, but during development even leaking a file handle or two isn't really that tragical - I'd rather have to restart my machine a few times than having to write exception safe code just so I don't leak a tiny file handle.

I think in the end, it all comes down to this: If you use exceptions for error handling, you have to embrace them, meaning you really have to structure your code around the use of exceptions, make sure it's exception safe, and that's a lot of mundane work and complexity just for a bit of error handling. And that doesn't even consider the potential performance issues that are still present on some platforms - zero cost exceptions are nice in theory, but in practice not really what they're promised to be last time I checked.
There's literally no C++ based game studio that I know of that doesn't turn exceptions off, and even more traditional software shops like Google don't use them.
Logged
oahda
Level 10
*****



View Profile
« Reply #806 on: August 07, 2016, 02:27:47 AM »

Well, you've convinced me. Tongue

I also basically managed to answer myself while writing up in my head the reply to the question as to why I would avoid pointers in these cases. I was going to say that it's basically about semantics, trying to express things in code instead of writing comments and documentation that might be ignored or forgotten; the way const expresses that something should not modify the state of an object, my references were intended to signify that the caller of the getter does not get ownership of the object returned and should not attempt to handle its memory. But I realise now that the const keyword can do that too: I can return a const pointer.
Logged

JWki
Level 4
****


View Profile
« Reply #807 on: August 07, 2016, 02:37:47 AM »

Well yeah from a semantics standpoint that's a sane way to go about it, however, speaking for myself, I usually just assume that a library that returns pointers also handles the memory that these point to unless specified otherwise. I agree that not having to specify anything outside of the code itself is usually better though.

Keep in mind that a const pointer can still be deleted however, and that constness in general is a really thin concept and doesn't help when the user really wants to mess with the code as it can simply be cast away.
Logged
oahda
Level 10
*****



View Profile
« Reply #808 on: August 07, 2016, 02:45:57 AM »

Yeah, I'm pondering some sort of smart pointer setup now... But there are semantic concerns there as well. If I store the children as unique pointers in a map (which is my current setup), that signifies that the map owns the children, but it might be iffy to return a smart pointer to a different scope. If I change it to shared pointers, it gets easier to return, but the semantics are less clear, and there might be overhead and stuff. Any ideas?

As for your second part, well, the semantics are mostly about communicating intentions. C++ generally allows one to circumvent stuff if one really wants to anyway, but at least it shouldn't happen by mistake if the semantics are clear. Because AFAIK at least I can't delete that const pointer without const casting it first, can I?

Another concern is consistency. Getting pointers sometimes, references sometimes, smart pointers sometimes, wrappers sometimes... And a messy mix of arrows and periods to access members. It can get a bit disorienting if it keeps changing with every function. But I suppose it's reasonable enough that returning a reference would signify that it definitely exists, while returning a pointer would signify that it could be null.
Logged

JWki
Level 4
****


View Profile
« Reply #809 on: August 07, 2016, 03:07:28 AM »

Going from bottom to top here, yes, even though a mix of pointers and references is a bit nasty sometimes, at least it allows to see if a function can even return null or not. However the mix is still somewhat nasty, C++ didn't get this part right and in my opinion should have settled for one or the other (and I'd root for pointers here). I'm also a big fan of consistency and therefore I just use pointers everywhere.

About deleting const pointers, afaik you can do that without casting. Haven't tried though.

Lastly, regarding smart pointers - VERY difficult topic. I was a fan until I used them for a full blown entity component system, not much of a fan after that. Firstly, the overhead is there, however how much of an issue that is should be profiled for average use cases of your API before considering it a critical point. BUT, from an architectural standpoint, they are a pain and become a mess really quick.
One thing that works well enough from a semantic standpoint is to just trust the user not to fuck with the memory you return to him, and have a clear ownership hierarchy defined. In a naive approach, you would maybe have a world owning entities, entities owning their components and that's it. You can express this ownership using unique_ptr. Now, every non-owning relationship is just expressed using raw pointers. This approach doesn't provide full safety, but to be honest trying to hold the users hand and making sure that he can't possible fuck around with stuff is one of the culprits that leads to convoluted and overcomplicated APIs. Usually, expecting the user of your API to be somewhat competent enough not to fuck shit up leads to much  cleaner and simpler code.

Keep in mind this 'naive' approach only works as long as you never want to move objects around in memory though. However, that would also break any smart pointer or reference based approach so I don't assume you do that anyways.
Logged
oahda
Level 10
*****



View Profile
« Reply #810 on: August 07, 2016, 03:23:05 AM »

Yeah, googling around, just returning regular pointers seems to be the most sane solution, albeit unattractive from a design point of view.

I guess another possible solution could involve iterators or something, basically allowing children to be accessed in an STL-compatible way more or less directly by defining begin() and end() and thereby enabling find() and so on. Don't know how much of a risk of overhead there is there. Maybe it is a decent solution, if done well? One issue is that I have more than one container member to return stuff from, tho...

Otherwise it does seem like const pointers are the way to go, I guess. And I guess the iterator thing only solves the particular problem of accessing children and not the overall design problem.

EDIT:
Would also have been really nice to have those new fancy ? operators Swift has in C++ to avoid ~the pyramid of doom~... Tongue Apparently C# added some of that too. Maybe in C++20... Roll Eyes Tho that's again something that could be done with exceptions, probably, but I'll try to stay away now, heh... Some other funky proposals. Ah, researching wonky ideas and reading what people come up with can be more fun than programming itself sometimes.
« Last Edit: August 07, 2016, 07:56:01 AM by Prinsessa » Logged

oahda
Level 10
*****



View Profile
« Reply #811 on: August 07, 2016, 09:20:05 AM »

Well, I was going to go ahead and try to make this change, but... Apparently Object *const is not OK; compiler says 'const' type qualifier on return type has no effect. Never actually stumbled across this before, probably because I didn't use pedantic warnings and warnings as errors the last time I programmed API's this way, and didn't program API's this way when I last did.

That's silly. Should I make my own lightweight reference wrapper or something? Like a Ref<T> that works more or less like the smart pointers (overloading -> and dereferencing * and implicit conversion to bool) but without any specific semantics, restrictions or implications other than that? It would basically be the same functionally as a pointer, but it would actually be able to prevent changing the value (it can only take a pointer at construction)... But I feel like there must be an STL smart pointer that does all of this already? Weak pointers? I'll look into it...

EDIT:
The answer seems to be that weak pointers are only possible to use with shared pointers. Welp. I honestly don't want to get into shared pointers, because reference counting makes me dizzy and I feel like that's much easier to mess up. I don't feel like I entirely understand the reasons people give, or how they always apply to all cases. I know that there won't be references randomly dying while the user is trying to access them in my use case, so a weak pointer referring to a unique pointer would be completely safe here.

I don't want the pointer to be accidentally deällocated because something went awry with shared pointers and reference counting, when the owner holding what are currently unique pointers should be the only entity with the power to do so. WTF

The docs do say that the value held won't be deällocated until "the last remaining shared_ptr owning the object is destroyed", tho, so maybe I've misunderstood something somewhere. I guess keeping shared pointers in a map where only the owner can access them should be safe, then, since they can't accidentally get deleted in there..?

Maybe I'm just burned from trying to deal with a bit of reference counting in Objective-C, where it seemed much less safe...

EDIT 2:
Well, I decided to let go of my paranoia and went ahead and modified my code to use shared pointers and it seems to be working, so I guess I'll trust it for now. It seems to be the most solid solution TBH, since thanks to returning const shared pointers I can guarantee unambiguous semantics (the ownership is rather clear since the original shared pointers are hidden private members that cannot be accessed directly, and because the shared pointers returned are const) while still being able to return null. Hand Thumbs Up Right I'm happy with this, I think!

EDIT 3:
Meh. Seems my paranoia was justified after all. If there are dangling references (shared pointers) around when I try to explicitly destroy a child, it might not actually get deällocated, just removed from the owner's map and shared pointer. So the ownership semantics didn't hold up after all. I think I'll have to implement my custom Ref<T> after all.
« Last Edit: August 07, 2016, 11:22:59 AM by Prinsessa » Logged

JWki
Level 4
****


View Profile
« Reply #812 on: August 07, 2016, 02:08:24 PM »

See that's why smart pointers are a pain in the ass. However what you're describing that Ref<T> as sounds like you really just want raw pointers here... Maybe you're a bit too obsessive trying to make your api safe?
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #813 on: August 07, 2016, 02:51:20 PM »

But I'm using references, so I can't do that. So there are only really two viable options: either forcing the user to first check manually by using something like hasChildByName(string), or by throwing an exception if the child doesn't exist so that it can be caught and handled.

An alternative is to define some "zero value" for the object type in question and return a reference to that.  I use this technique whenever possible, but depending on the properties of the object, you can't do it.

Something akin to this:

Code:
class MyThingy
{
    // Whatever
};

constexpr MyThingy no_thingy;

constexpr bool operator == (const MyThingy &lhs, const MyThingy &rhs);

const MyThingy &find_thingy(int key)
{
    if (cant_find_it)
    {
        return no_thingy;
    }

    return found_thingy;
}

// Client code
const MyThingy &thingy = find_thingy(key);

if (thingy == no_thingy)
{
    // didn't find it!
}

Like I said, I use this sort of thing all the time.  I like defining various constant values for my types whenever I can.
Logged



What would John Carmack do?
oahda
Level 10
*****



View Profile
« Reply #814 on: August 08, 2016, 12:14:28 AM »

Decent idea, I guess, but I can't really do that since there's polymorphism involved (getComponent<T>() for example). :c

Is there anything fundamentally bad about writing a small Ref<T> wrapper tho? It's basically the same as returning a raw pointer or STL smart pointer, only I have complete control over its scope and lifetime and ownership the way I want it, in a way that apparently neither raw pointers or the STL pointers can solve properly the way they work ATM. q_q

The implementation would basically just be this (it does not own the pointer and does not manage its memory):

Code:
template<typename T> class Ref
{
public:
Ref(T *const pointer = nullptr) : m_pointer(pointer) {}
Ref(const Ref<T> &o) : m_pointer(o.m_pointer) {}
Ref<T> &operator=(const Ref<T> &) = delete;

T &operator*() { return *m_pointer; }
T *operator->() { return m_pointer; }
operator bool() { return m_pointer; }

private:
T *m_pointer{nullptr};
};

That's it. I'd return const Ref<T> and the ownership would be written in stone right there in the code itself and basically all issues are solved..?
Logged

JWki
Level 4
****


View Profile
« Reply #815 on: August 08, 2016, 12:17:39 AM »

What exactly does that type give you that a raw pointer doesn't? It has exactly the same semantics as far as I see, except that it's not copy assignable which is a bit weird considering that you allow copy construction.
Logged
oahda
Level 10
*****



View Profile
« Reply #816 on: August 08, 2016, 12:30:23 AM »

What exactly does that type give you that a raw pointer doesn't? It has exactly the same semantics as far as I see, except that it's not copy assignable which is a bit weird considering that you allow copy construction.
Indeed. Only issue is, like I said earlier, apparently * const "has no effect" according to the compiler. Which means I can't actually encode that the callee does not own the pointer. ;-;

Copy construction is just so that the callee can store the value and do things with the object after getting it. Dunno if I should be using a move constructor instead. Haven't really looked into those yet since they were invented.

I'm well aware that this isn't quite orthodox, it's just that I can't seem to find an orthodox solution that doesn't bother me somehow.
Logged

JWki
Level 4
****


View Profile
« Reply #817 on: August 08, 2016, 12:42:13 AM »

Well as you can actually delete const pointers without casting, returning a *const doesn't encode that anyways.
As I see it, Ref<T> doesn't actually gain you or the user anything - besides a bit of syntactic sugar that essentially only says "please don't delete this". What it does do however is introduce another layer of indirection that the user has to reason about because they will not instantly know that Ref<T> doesn't actually do anything special so they will be asking themselves what it actually does and then be confused when they find out it does nothing. The callee can still just delete the object that Ref<T> references by taking the address of what the * operator returns and calling delete on that, so it's not actually safer.
The only thing you gain is a tiny tiny bit more expressiveness MAYBE, at the cost of introducing smoke and mirrors for the user.
Would it really be so bad just to document the fact that the user doesn't own the pointer? That's what documentation and comments are for, after all. It just seems like a really convoluted solution to me for something that a simple comment could solve.

On the topic of move constructors, no, one of these wouldn't buy you anything here, they're basically only useful in cases where your type can be somewhat non-trivial to copy because it manages memory or external resources.
However, I should add that as a user of that type I'd be pretty confused and annoyed by the fact that it's immutable - it's some kind of 'smart' pointer after all, even if it doesn't do anything, so I'd expect to be able to reassign it at will.


EDIT:
Oh and on that * const has no effect thing:
That's because you return something by copy (a pointer), so the expression is actually an rvalue - and (non-class type) rvalues are inherently immutable, so the standard states that they may never be const qualified. That's why your compiler tells you it doesn't actually have an effect.
To back this up, see this stackoverflow question:
http://stackoverflow.com/questions/1607188/why-is-a-type-qualifier-on-a-return-type-meaningless
« Last Edit: August 08, 2016, 12:55:53 AM by JWki » Logged
oahda
Level 10
*****



View Profile
« Reply #818 on: August 08, 2016, 01:20:02 AM »

Yeah, I basically agreed with you even as I wrote my post TBH, but I wanted to ask anyway. I think you're right.

And thanks for explaining the const pointer thing. Of course. That's why I never return const bool either. Same thing. Thought about it somewhat differently because the const is in a different place in this case, I guess.
« Last Edit: August 08, 2016, 01:25:39 AM by Prinsessa » Logged

JWki
Level 4
****


View Profile
« Reply #819 on: August 08, 2016, 01:24:55 AM »

Yeah I think it's very important to discuss your designs and architectures with others that may have a totally different point of view and also are a bit more detached from the design as they didn't come up with it so they're not biased by creators pride I guess. I enjoy this kind of discussion tremendously.

And well const is a weird thing.
Logged
Pages: 1 ... 39 40 [41] 42 43 ... 69
Print
Jump to:  

Theme orange-lt created by panic