Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411528 Posts in 69377 Topics- by 58433 Members - Latest Member: Bohdan_Zoshchenko

April 28, 2024, 10:13:15 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)General thread for quick questions
Pages: 1 ... 48 49 [50] 51 52 ... 69
Print
Author Topic: General thread for quick questions  (Read 135527 times)
JWki
Level 4
****


View Profile
« Reply #980 on: February 26, 2017, 02:49:54 PM »

Been trying to find some reading, but I don't feel like every question has been answered.

Memorywise, I know virtual inheritance isn't a huge blow, since there is only one vtable and each instance only store a pointer. My question regards virtual functions. The way I've managed to organise stuff right now, my code always knows the exact type of each object, and the objects do not derive from a common base class and so there is no virtualness involved.

However, there are a couple of functions I would like to have on objects that each could implement individually. The way it works right now I can actually have non-virtual inheritance and just hide functions with the same name in a non-virtual base class since I'm always operating on the exact (in this scenario derived) type, and so there is no confusion: the redefined function always gets called. But this might be bad practice.

I wonder, if the compiler always sees the exact type and never needs to resolve virtual calls down the chain, but can go directly to the version it needs to call, is this any less efficient than hiding instead of overriding? How does virtual inheritance and overridden functions work out in memory compared to the non-virtual version? Is it worse for caching? And will the compiler also be able to inline some of these virtual functions since it knows the exact type and no resolution needs to happen?

Virtual dispatch does come with a runtime cost because of the pointer chasing that has to happen when finding the correct implementation.
The compiler CAN devirtualize calls if you present it with optimal conditions to find the correct implementation at runtime but usually those situations are not where you use virtual dispatch because if you know the types, why use virtual at all.
Logged
oahda
Level 10
*****



View Profile
« Reply #981 on: February 27, 2017, 02:23:52 AM »

because if you know the types, why use virtual at all.
That's my thinking too, but people online seemed to view it as a bad practice to hide functions instead, so I thought maybe there were good reasons to avoid that.

I misworded myself when I said down the chain, BTW. I meant up the chain, which would be the case if I were calling the virtual function from a base class pointer that actually points to a derived object. But in my implementation I'm actually working directly on derived non-pointers, so the exact type is always visible and no resolution should need to happen at runtime if the compiler realises this and can optimise for this, which some of my search results seemed to indicate too, but I wasn't sure.
Logged

JWki
Level 4
****


View Profile
« Reply #982 on: February 27, 2017, 02:50:14 AM »

because if you know the types, why use virtual at all.
That's my thinking too, but people online seemed to view it as a bad practice to hide functions instead, so I thought maybe there were good reasons to avoid that.

I misworded myself when I said down the chain, BTW. I meant up the chain, which would be the case if I were calling the virtual function from a base class pointer that actually points to a derived object. But in my implementation I'm actually working directly on derived non-pointers, so the exact type is always visible and no resolution should need to happen at runtime if the compiler realises this and can optimise for this, which some of my search results seemed to indicate too, but I wasn't sure.

Don't count on the compiler doing that - in your case, working directly on the derived type SHOULD help the compiler to notice that you want to call those implementations and it SHOULD be able to mostly devirtualize those calls, but I wouldn't rely on compiler behaviour to match your expectations always.
So you're actually shadowing methods in the derived classes? So you have like a base class that has a method foo(void) -> void and you have a function foo(void) -> void in the derived class but they're not virtual?
Because if that's the case, I understand what you mean by "bad practice" - but I'd also say fuck that honestly, whatever works for you is fine. Unless you want to put this out for others to use, in that case it could be somewhat confusing to the user.

What's the base class for if you don't use the virtual dispatch? Just to define the interface that all the different types offer?
Logged
oahda
Level 10
*****



View Profile
« Reply #983 on: February 27, 2017, 03:04:12 AM »

So you have like a base class that has a method foo(void) -> void and you have a function foo(void) -> void in the derived class but they're not virtual?
Yep.

Unless you want to put this out for others to use, in that case it could be somewhat confusing to the user.
Yeah, nobody will be calling these directly/manually anyway tho, as they're used internally by the engine in specific places.

What's the base class for if you don't use the virtual dispatch? Just to define the interface that all the different types offer?
Yeah, just to make sure the methods exist so that the compiler doesn't yield an error when I try to call one of them on an object whose type hasn't defined a custom/explicit implementation, so that I/users don't have to manually declare a bunch of dummy functions, that might mostly be empty if they're not needed for that particular type, in every new class, or even need to know how many are available, so if I add more functions to the engine later, old classes won't all need updating to be compatible.

(can I also assume/hope that if the functions have been declared and defined with completely empty bodies in the base class's header the compiler will optimise out the call completely if the derived types don't hide them, BTW?)
Logged

JWki
Level 4
****


View Profile
« Reply #984 on: February 27, 2017, 04:27:28 AM »

Last question first, with optimizations turned on the compiler usually should not generate any output for stubs being called, yeah.

What you're doing doesn't seem problematic to me, if it works for you why not do it.
Personally I feel it's a bit iffy which is usually a sign that there might be a better solution to achieve what you're trying to achieve though.
Logged
oahda
Level 10
*****



View Profile
« Reply #985 on: February 27, 2017, 04:43:30 AM »

The idea I thought of initially was to just register objects that should have certain functions called with callbacks in some kind of list instead, but if it happens every frame during the main loop, I was wondering, again, whether it'd be worse for the cache to not just have the functions directly in the continuous object and call them directly on them as I loop through them, instead of having to look them up in a different list? I dunno.
Logged

JWki
Level 4
****


View Profile
« Reply #986 on: February 27, 2017, 05:08:22 AM »

Can you try and give a concrete use case maybe? I understand what you're trying to do but I feel discussing it might be easier with an example to hang on to.
Logged
oahda
Level 10
*****



View Profile
« Reply #987 on: February 27, 2017, 06:09:45 AM »

Basically events that the objects can choose to do something with, let's say the traditional update and draw for simplicity's sake, and perhaps also things like collisions and so on, and perhaps game-specific events.

So either each object has an update() function inherited (virtually or not) from the base class, which it overrides or hides to provide a custom implementation, or I keep a list for each type of event, so that individual objects or types of objects can register themselves with callbacks and then the lists are iterated through for each event to invoke all the callbacks, like scene.registerCallbackOnUpdate(this, myUpdate) or whatever. Or some third option, perhaps.
Logged

JWki
Level 4
****


View Profile
« Reply #988 on: February 27, 2017, 06:24:03 AM »

What kinda goes beyond me is why you need the dispatch at all when you know the type.
Because if you know the type, there's no point in having a per-object update function - just iterate over all the objects and do what you'd do in the update on them.

Because atm, what you seem to have is like an array per type and in the update you iterate over each array and call update() on each instance.
But if you know the type of the instances, why do you need the update() call at all instead of just doing the work you want to do on each instance?
Logged
oahda
Level 10
*****



View Profile
« Reply #989 on: February 27, 2017, 07:39:09 AM »

Woops, I truly misspoke there: of course I don't need one callback per instance, but only one per type. Sorry for the confusion. x_x

EDIT:
But I guess it boils down to convenience. It's nice just to declare the function and it works, and if I don't it still works. To have to manually register every function and risk forgetting to or doing something wrong seems... unnecessary. Unless there is some template magic I can do to only conditionally try and invoke functions if they actually exist in the particular class at hand... Tongue
« Last Edit: February 27, 2017, 07:47:46 AM by Prinsessa » Logged

JWki
Level 4
****


View Profile
« Reply #990 on: February 27, 2017, 07:53:00 AM »

template magic I can do to only conditionally try and invoke functions if they actually exist in the particular class at hand... Tongue

There is. It's called SFINAE.
Let me see if I can whip up a quick example:

Code:

this code is loading....

Logged
oahda
Level 10
*****



View Profile
« Reply #991 on: February 27, 2017, 07:57:27 AM »

Found one myself, actually, so I think I'm going to try it. But do load your code—would like to see your suggestion too! Coffee
Logged

zilluss
Level 1
*



View Profile
« Reply #992 on: February 27, 2017, 08:07:36 AM »

Visitor pattern where you register your callbacks to an update-object maybe?
(Languages with multiple dispatch are a bliss)
Logged

@zilluss | Devlog  Hand Point Right
JWki
Level 4
****


View Profile
« Reply #993 on: February 27, 2017, 08:12:12 AM »

Code:
Highly likely what you found is this:
http://stackoverflow.com/questions/12015195/how-to-call-member-function-only-if-object-happens-to-have-it

So yeah that's basically what I have:

[code]
template <class T>
struct UpdateWrapper
{

template <class A>
static std::true_type test(void (A::*)(void)) {
return std::true_type();
}

template <class A>
static decltype(test(&A::update)) test(decltype(&A::update), void*)
{
typedef decltype(test(&A::update)) return_type;
return return_type();
}

template <class A>
static std::false_type test(...)
{
return std::false_type();
}

typedef decltype(test<T>(0, 0)) type;
static const bool value = type::value;

static void tryCallUpdate(T& t, std::true_type)
{
t.update();
}

static void tryCallUpdate(...)
{
// no-op
}

static void tryCallUpdate(T& t)
{
tryCallUpdate(t, type());
}
};

template <class T>
void TryUpdateIfExists(T& t)
{
UpdateWrapper<T>::tryCallUpdate(t);
}



Used like this

Code:

struct HasUpdate
{
void update() {
printf("Update!\n");
}
};

struct HasNoUpdate
{
void noUpdate()
{
return;
}
};

...

HasUpdate a;
HasNoUpdate b;

TryUpdateIfExists(a);
TryUpdateIfExists(b);


EDIT: I have to stop typing in code fields like I do in an IDE I keep on submitting changes before I'm done.
So yeah that'll print "Upate!" once and do nothing for b because it doesn't have the corresponding method.

I have to add while this is really handy at times it can get annyoing to debug. It's also one of the things C++ is both loved and hated for.[/code]
« Last Edit: February 27, 2017, 08:17:17 AM by JWki » Logged
oahda
Level 10
*****



View Profile
« Reply #994 on: February 27, 2017, 08:15:39 AM »

Thanks! So I see this ellipsis ... operator without any variadic template present in both your example and the one I found (different one from your link, actually!)... I've never seen this before. What is it? Is it C++11 or is it the thing you see in old C macros? Is it an extension of variadic templates that I never learned?
Logged

JWki
Level 4
****


View Profile
« Reply #995 on: February 27, 2017, 08:19:30 AM »

Thanks! So I see this ellipsis ... operator without any variadic template present in both your example and the one I found (different one from your link, actually!)... I've never seen this before. What is it? Is it C++11 or is it the thing you see in old C macros? Is it an extension of variadic templates that I never learned?

Neither, you can have variadic lists of arguments in functions since C, like in printf, without any macros.
It's essentially about having a mismatch in signatures in this case.
Logged
oahda
Level 10
*****



View Profile
« Reply #996 on: February 27, 2017, 08:27:28 AM »

Wow. Never knew! I hadn't really pondered nor seen it since I first started learning C++98 some ten years ago and only ever saw it in printf and a couple of macros. Never seen it in "proper" C++ code before. Is there a reference page on it somewhere that goes into more detail?

Here's the one I found, BTW (the second example specifically for C++11), which is nicely short while also allowing me to look for a particular signature: http://stackoverflow.com/a/18574534/5012939

This one:

Code:
template <typename T>
class has_size {
private:
  typedef char Yes;
  typedef Yes No[2];

  template<typename C> static auto Test(void*)
    -> decltype(size_t{std::declval<C const>().size()}, Yes{});

  template<typename> static No& Test(...);

public:
    static bool const value = sizeof(Test<T>(0)) == sizeof(Yes);
};
Logged

JWki
Level 4
****


View Profile
« Reply #997 on: February 27, 2017, 08:32:38 AM »

Yeah that's nice to do the check without directly calling it - mine doesn't expose the information whether the class has the specified member but rather tries to call it and does nothing when it can't.
While allowing you to specify the signature btw.

But yeah that's about how you could approach that.
Logged
oahda
Level 10
*****



View Profile
« Reply #998 on: February 27, 2017, 08:37:55 AM »

Sure, I didn't mean to say that yours doesn't. Tongue
Logged

JWki
Level 4
****


View Profile
« Reply #999 on: February 27, 2017, 08:41:10 AM »

Sure, I didn't mean to say that yours doesn't. Tongue

To be fair I wouldn't know how to do it without allowing that xD  so it's not like I'm particulary proud of that it just came about.

If you want more template magic, I can blow your mind with a 100 line perfect zero overhead delegate class if you want, too.  Wink
But I'd be really careful with these tricks, when I used to use this stuff extensively my co-programmers did complain a lot because it produces horrible horrible compiler errors if something goes wrong.
Logged
Pages: 1 ... 48 49 [50] 51 52 ... 69
Print
Jump to:  

Theme orange-lt created by panic