Sign in to follow this  
InsaneBoarder234

Passing constructor call to function - Is this safe?

Recommended Posts

I just created a quick program to check for safe execution of this code and it seems safe (using std::cout to follow program flow), but I wanted a second opinion on it.
class SomeClass
{
    //...
};

void SomeFunc(SomeClass *pObj)
{
    //...
}

int main()
{
    SomeFunc(&SomeClass());
    return 0;
}

Is the behaviour of this code defined by the C++ standards? Or should I be creating the SomeClass object either by having "SomeClass someclass;" or "SomeClass *psomeclass = new SomeClass();" at the start of main()?

Share this post


Link to post
Share on other sites
SomeClass() creates a temporary object that is destroyed after SomeFunc() returns. It is safe to use it only inside SomeFunc(), but it's not safe to store the pointer "pObj" for later use. You can see how this work by observing how objects get created/destroyed:


class Foo
{
public:
int x;
Foo(int x):x(x)
{
cout<<"Foo object created"<<endl;
}
virtual ~Foo()
{
cout<<"Foo object destroyed"<<endl;
}
};

void Bar(Foo* foo)
{
cout<<"foo.x = "<<foo->x<<endl;
}

int _tmain(int argc, _TCHAR* argv[])
{

Bar(&Foo(23));
cout<<"Next line after Bar()"<<endl;
return 0;
}



You see that the output is:

Foo object created
foo.x = 23
Foo object destroyed
Next line after Bar()

Share this post


Link to post
Share on other sites
Don't really see why you're doing this as you could be introducing one of those hard to find bugs. For void SomeFunc(SomeClass *pObj) you are passing an instance of SomeClass that can be NULL. Unless you have provided a check for this NULL instance of SomeClass and it does turn out to be NULL your program will crash. There are a number of alternatives:

1. Create a global pointer to some class and get it to0 locally point to an instance of this same class in your function. This is also bad practice, since the instance in your function will go out of scope and you global pointer will become undefined.

2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.

3. Have a pointer to SomeClass declared locally in your function and have it point to the global pointer/non-pointer instance. I see little reason for this implementation.

That's my two pennies! :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
Don't really see why you're doing this as you could be introducing one of those hard to find bugs. For void SomeFunc(SomeClass *pObj) you are passing an instance of SomeClass that can be NULL. Unless you have provided a check for this NULL instance of SomeClass and it does turn out to be NULL your program will crash. There are a number of alternatives:

1. Create a global pointer to some class and get it to0 locally point to an instance of this same class in your function. This is also bad practice, since the instance in your function will go out of scope and you global pointer will become undefined.

2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.

3. Have a pointer to SomeClass declared locally in your function and have it point to the global pointer/non-pointer instance. I see little reason for this implementation.

That's my two pennies! :)


Or you could try this:


class SomeClass
{
//...
};

void SomeFuncByPointer_preferToUse_SomeFuncByReference_instead(SomeClass *pSomeClass)
{
// use *pSomeClass
}

void SomeFuncByReference(SomeClass &someClass)
{
// use someClass
}

int main()
{
// code here

{ // inner scope!

SomeClass temp;
SomeFuncByReference(temp);
SomeFuncByPointer_preferToUse_SomeFuncByReference_instead(&temp);

} // watch temp disappear!

// more here
return 0;
}





We could also make the functions const if they do not modify their argument ( and seeing as you are happy to pass in a temporary, they probably don't ).

Thats just how I would do it, passing the address of a temporary gives me a warning so I would avoid it even if it is guarenteed to work.

Share this post


Link to post
Share on other sites
Quote:
Original post by InsaneBoarder234
Is the behaviour of this code defined by the C++ standards?


It isn't, as you cannot take the address of a temporary variable (you may only take the address of an lvalue). The result is either undefined behavior or outright compiler complaints.

It is, however, defined by one of the Visual C++ extensions to the C++ languages, which allows such use of temporary variables. Thus, your code works but is not portable.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
...
2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.
...


Oh HELL no.

1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.
3) That's not going to work if there's more than one SomeClass.
4) Oh HELL no.

OP: If SomeFunc allways operates on an object, consider using a reference instead ala:

void SomeFunc( SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


Of course, if it's operating on a temporary, chances are it shouldn't be modifying the variable (since any changes would be immediately discarded), so a reference-to-const might be more appropriate:

void SomeFunc( const SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


The only way for a reference to become null is through dereferencing a null pointer, which being undefined behavior according to the C++ standard, means you can't have null references in a well formed program. Generally, one should check to ensure the pointer is non-NULL before dereferencing it into a reference.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by Mathematix
...
2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.
...


Oh HELL no.

1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.
3) That's not going to work if there's more than one SomeClass.
4) Oh HELL no.

OP: If SomeFunc allways operates on an object, consider using a reference instead ala:

void SomeFunc( SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


Of course, if it's operating on a temporary, chances are it shouldn't be modifying the variable (since any changes would be immediately discarded), so a reference-to-const might be more appropriate:

void SomeFunc( const SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


The only way for a reference to become null is through dereferencing a null pointer, which being undefined behavior according to the C++ standard, means you can't have null references in a well formed program. Generally, one should check to ensure the pointer is non-NULL before dereferencing it into a reference.


Notice that I said "This appears to be the best option for what you want to do", which is not the same as it being good code practice.

Quote:
Original post by MaulingMonkey
1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.


Of couse, and I didn't imply otherwise. The point of making it global is that you will create an instance in one place and destroy the instance at anopther single place in the code.

You haven't pointed out why it wouldn't solve the problem!

Quote:
Original post by MaulingMonkey
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.


What?! If you point to the global, as I was trying to get across, then make sure that you're not pointing to it again when leaving the function, why should that be a problem?

Again, how does this add to the problem?

Quote:
Original post by MaulingMonkey
3) That's not going to work if there's more than one SomeClass.


You're pointing out a problem that was not described, in that multiple instances of SomeClass are required. If this was the case then I would have offered another solution via a design pattern.

You're quite right, though. I should have pointed out that this will cause problems for multiple instances of SomeClass.

Quote:
Original post by MaulingMonkey
4) Oh HELL no.


Globals aren't necessarily a bad thing, and are used in production code when justified. What makes them appear bad is poor implementations elsewhere in the code. :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
Quote:
Original post by MaulingMonkey
Quote:
Original post by Mathematix
...
2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.
...


Oh HELL no.

1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.
3) That's not going to work if there's more than one SomeClass.
4) Oh HELL no.

OP: If SomeFunc allways operates on an object, consider using a reference instead ala:

void SomeFunc( SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


Of course, if it's operating on a temporary, chances are it shouldn't be modifying the variable (since any changes would be immediately discarded), so a reference-to-const might be more appropriate:

void SomeFunc( const SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


The only way for a reference to become null is through dereferencing a null pointer, which being undefined behavior according to the C++ standard, means you can't have null references in a well formed program. Generally, one should check to ensure the pointer is non-NULL before dereferencing it into a reference.


Notice that I said "This appears to be the best option for what you want to do", which is not the same as it being good code practice.

Quote:
Original post by MaulingMonkey
1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.


Of couse, and I didn't imply otherwise. The point of making it global is that you will create an instance in one place and destroy the instance at anopther single place in the code.

You haven't pointed out why it wouldn't solve the problem!


The problem, as you stated it, was:

Quote:

Don't really see why you're doing this as you could be introducing one of those hard to find bugs. For void SomeFunc(SomeClass *pObj) you are passing an instance of SomeClass that can be NULL. Unless you have provided a check for this NULL instance of SomeClass and it does turn out to be NULL your program will crash. There are a number of alternatives:


Your alternative obviously doesn't solve the problem you described(NULL pointers). "Creating an instance in one place and destroy it at another" is another problem entirely. Doesn't have anything to do with whether the pointer is NULL or not. In that case, you could just create an instance, pass it to the function as an argument, and destroy it at another place, like the OP said in hist first post:

Quote:

Or should I be creating the SomeClass object either by having "SomeClass someclass;" or "SomeClass *psomeclass = new SomeClass();" at the start of main()


Communicating with the function via a global pointer is completely unreasonable and solves absolutely nothing. You're just substituting the argument list with a global variable, and nothing more. There's absolutely no reason to do that, and about a dozen reasons not to do it.

Share this post


Link to post
Share on other sites
Thanks for the replies all. It seems I was overcomplicating things a bit with my project and this code is unnecessary, sorry!

Either way, I have learnt something! It's probably best to use a const reference if I end up implementing something like this in the future. Thanks again for the replies.

Share this post


Link to post
Share on other sites
Beware global variables. Once upon a time they were nothing special, but in these days of multi-core machines and corresponding shift towards multithreaded code, they're notable as the bane of thread-safe programming.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
Notice that I said "This appears to be the best option for what you want to do", which is not the same as it being good code practice.


If it's not good coding practice, it's not very likely to be the "best" option now is it? After all, good coding practice is all about solving problems in a best manner possible.

Quote:
Quote:
Original post by MaulingMonkey
1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.


Of couse, and I didn't imply otherwise. The point of making it global is that you will create an instance in one place and destroy the instance at anopther single place in the code.

You haven't pointed out why it wouldn't solve the problem!


You stated the problem, as mikeman points out, as being a hard to find bug (lies - use a debugger) deriving from the possibility of the pointer being NULL. As I just said, global pointers suffer this exact same possibility. As such, your "solution" does nothing to alieviate the indicated problem. Is that explicit enough?

Quote:
Quote:
Original post by MaulingMonkey
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.


What?! If you point to the global, as I was trying to get across, then make sure that you're not pointing to it again when leaving the function, why should that be a problem?


At this point, I'm lost as to what you're trying to describe. Post some code to illustrate what you mean? Did you mean "pointer-to-global" instead of "global pointer" perchance?

Quote:
Quote:
Original post by MaulingMonkey
3) That's not going to work if there's more than one SomeClass.


You're pointing out a problem that was not described, in that multiple instances of SomeClass are required.


What wasn't described is a situation where multiple instance of SomeClass are not required. Given that we're dealing with abstracts such as SomeClass, Foo, Bar, etc - it'd be insane not to think we need to address the general case - which, if the standard library is any judgement, consisting 99%+ of classes that are not limited to one instance - would seem to be "any number of instances" rather than "one, only one, and nothing more than one instance".

Quote:
Quote:
Original post by MaulingMonkey
4) Oh HELL no.


Globals aren't necessarily a bad thing, and are used in production code when justified. What makes them appear bad is poor implementations elsewhere in the code. :)


When justified. Justification that seems to be lacking, in this case. The entire proposal makes me go "What? Why? You want to add a bunch of assumptions to our code (specific global variables instead of any old variable the client wants to pass along as an argument), and the only apparent justification falls completely flat (that it somehow magically makes it unable to be NULL, less likely to be NULL, or anything else along those lines).

Hence the reaction. Adding globals without answering the "why".

Here's a good why: My program depends on this logger in multiple places throughout the code, and will not need multiple loggers in this architecture's forseeable future (it's a prototype or the program will need a full rehaul of architecture anyways if our original requirements have changed that drastically). As such, the simplicity of a global over dependancy injection (or alternative method) outweighs the risk of having to recode that assumption.

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
Quote:
Original post by Mathematix
Quote:
Original post by MaulingMonkey
Quote:
Original post by Mathematix
...
2. Create the aforementioned global pointer to SomeClass and use it in your functions as required. This appears to be the best option for what you want to do.
...


Oh HELL no.

1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.
2) Global pointers can be modified from ANYWHERE, and as such, it can be harder to figure out what, for example, might be modifying the pointer in question to become NULL in the first place, so it adds to the problem you described.
3) That's not going to work if there's more than one SomeClass.
4) Oh HELL no.

OP: If SomeFunc allways operates on an object, consider using a reference instead ala:

void SomeFunc( SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


Of course, if it's operating on a temporary, chances are it shouldn't be modifying the variable (since any changes would be immediately discarded), so a reference-to-const might be more appropriate:

void SomeFunc( const SomeClass & Obj )
{
//...
}

int main()
{
SomeFunc( SomeClass() );
}


The only way for a reference to become null is through dereferencing a null pointer, which being undefined behavior according to the C++ standard, means you can't have null references in a well formed program. Generally, one should check to ensure the pointer is non-NULL before dereferencing it into a reference.


Notice that I said "This appears to be the best option for what you want to do", which is not the same as it being good code practice.

Quote:
Original post by MaulingMonkey
1) Global pointers can be just as NULL as function argument pointers, so your proposal dosn't solve the problem you described.


Of couse, and I didn't imply otherwise. The point of making it global is that you will create an instance in one place and destroy the instance at anopther single place in the code.

You haven't pointed out why it wouldn't solve the problem!


The problem, as you stated it, was:

Quote:

Don't really see why you're doing this as you could be introducing one of those hard to find bugs. For void SomeFunc(SomeClass *pObj) you are passing an instance of SomeClass that can be NULL. Unless you have provided a check for this NULL instance of SomeClass and it does turn out to be NULL your program will crash. There are a number of alternatives:


Your alternative obviously doesn't solve the problem you described(NULL pointers). "Creating an instance in one place and destroy it at another" is another problem entirely. Doesn't have anything to do with whether the pointer is NULL or not. In that case, you could just create an instance, pass it to the function as an argument, and destroy it at another place, like the OP said in hist first post:

Quote:

Or should I be creating the SomeClass object either by having "SomeClass someclass;" or "SomeClass *psomeclass = new SomeClass();" at the start of main()


Communicating with the function via a global pointer is completely unreasonable and solves absolutely nothing. You're just substituting the argument list with a global variable, and nothing more. There's absolutely no reason to do that, and about a dozen reasons not to do it.


You guys are completely missing my point. The original source was:


class SomeClass
{
//...
};

void SomeFunc(SomeClass *pObj)
{
//...
}

int main()
{
SomeFunc(&SomeClass());
return 0;
}



Now remember here that we do not know the definition for SomeFunc(), and since we do not know that, you cannot claim that the code that you've provided is any more safe than mine.

Quote:
Original post by mikeman
Your alternative obviously doesn't solve the problem you described(NULL pointers).


My solutions were suggestions on a viable solution to having multiple pointers to a single object. Surely you must see the logic in that?

Quote:
Original post by mikeman
"Creating an instance in one place and destroy it at another" is another problem entirely. Doesn't have anything to do with whether the pointer is NULL or not.


Given that a pointer can be NULL and a reference can't, there is no point in risking the potential for this to occur. UIf your pointer fails to be allocated in one place then you can introduce code to handle the exception at that point, rather than waste time going any further down the pipeline.

Quote:
Original post by mikeman
Communicating with the function via a global pointer is completely unreasonable and solves absolutely nothing.


Ummm, okay. If you say so.

Quote:
Original post by mikeman
You're just substituting the argument list with a global variable, and nothing more.


And that was my intention.

Quote:
Original post by mikeman
There's absolutely no reason to do that, and about a dozen reasons not to do it.


And the dozen reasons are?

You do realise that even though you are objecting, my suggestions will still work. ;)

Share this post


Link to post
Share on other sites
Yes, your suggestions will work. But they are not the best idea, IMO.

Quote:

Quote:

You're just substituting the argument list with a global variable, and nothing more.

And that was my intention


You have yet to prove what you gain from that.

The problem here is the lifetime of the temporary in main. The calling code need not be modified, the problem applies to any function taking a temporary variable reference.

IMHO ([smile]) my way (with references, const references if possible ) is the simplest solution.
Can you give me any reason why this is not the case?

Share this post


Link to post
Share on other sites
You've completely lost me. First you provide alternatives you claim solve this problem:
Quote:

Don't really see why you're doing this as you could be introducing one of those hard to find bugs. For void SomeFunc(SomeClass *pObj) you are passing an instance of SomeClass that can be NULL. Unless you have provided a check for this NULL instance of SomeClass and it does turn out to be NULL your program will crash.


...then you say that those alternatives do not solve the above problem at all but are a "viable solution" for handling multiple pointers to the same object, then you talk about references that can't be NULL whereas minutes ago you were talking about global pointers. I can't really understand what you're trying to solve here. Catching NULL pointer *is* a problem, but your alternatives don't solve that. Other than that, all we know is that we deal with a function that takes a pointer to an object as an argument. We know nothing more, so I *really* don't understand what kind of problem we're trying to solve here.

Quote:

You do realise that even though you are objecting, my suggestions will still work. ;)


Actually, now I don't even realize what your suggestions are. Global pointers,references, what? Perhaps you could just write the code of your solution so we can see what you mean?

By the way:
Quote:

Now remember here that we do not know the definition for SomeFunc(), and since we do not know that, you cannot claim that the code that you've provided is any more safe than mine.


I haven't provided any code at all. The example I gave just demonstrated when the temp objects are created and destroyed.

Share this post


Link to post
Share on other sites
Quote:

Now remember here that we do not know the definition for SomeFunc(), and since we do not know that, you cannot claim that the code that you've provided is any more safe than mine.


Yes we can, provided there are no globals interacting with the function. [smile]

The function, taking a reference to a SomeClass, cannot store the address of a temporary unless it makes use of some global. So by omitting globals entirely, and seeing as there are no other parameters which could potentially be used to store a reference to the temporary, there can be no trailing references. This is perfect.

Your method introduces a global, which casts doubt. If the global is only used for communicating an argument to the function, then why isnt it an argument in the argument list?

The "solutions" you posted ( all of them contain globals? ) only introduce doubt on what would otherwise be a safe function for temporaries.

Share this post


Link to post
Share on other sites
Guys

You have offered your solutions and I have offered mine. I'm not in the mood for an ego trip. Different programmers do things different ways.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
Guys

You have offered your solutions and I have offered mine. I'm not in the mood for an ego trip. Different programmers do things different ways.


But what *is* your solution? What *is* the problem you're trying to solve? Nobody is in an ego trip, we just don't understand what you're saying. Just post a short snippet of your #2 solution.

Share this post


Link to post
Share on other sites

class SomeClass
{
//...
};

void SomeFunc(SomeClass *pObj) // This function which is not a member of SomeClass
{ // expects a valid pointer to
return;
}

int main()
{
SomeClass *anInstance;

SomeFunc(anInstance); // Pass uninitialised pointer to SomeFunc(). Code bombs-out at runtime.
return 0;
}




The above can simply be fixed by simply having


class SomeClass
{
//...
};

void SomeFunc(SomeClass *pObj) // This function which is not a member of SomeClass
{ // expects a valid pointer to
return;
}

int main()
{
SomeClass *anInstance;

try
{
anInstance = new SomeClass;
}
catch (...)
{
cout << "Could not allocate instance of class" << endl;
exit(1);
}

SomeFunc(anInstance); // Pass uninitialised pointer to SomeFunc(). Code runs fine.
return 0;
}




Depending on what you want to do you can declare anInstance locally within main() or globally.

It's simple and it works if you use it carefully.

Share this post


Link to post
Share on other sites
????

That's a valid solution(well, the use of exceptions here doesn't seem exactly right, but whatever), but you don't communicate with the function via a global. You do it properly like everyone else proposed, via an argument. Exactly like I said some posts ago:

Quote:

In that case, you could just create an instance, pass it to the function as an argument, and destroy it at another place, like the OP said in hist first post


We were arguing all along about using globals, and now you present a solution that doesn't use one?

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
We were arguing all along about using globals, and now you present a solution that doesn't use one?



I have presented a solution that can use one, depending on the implementation!

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
Quote:
Original post by mikeman
We were arguing all along about using globals, and now you present a solution that doesn't use one?



I have presented a solution that can use one, depending on the implementation!


Dude, all solutions that use pointers can use global pointers! There isn't any solution that requires that the variable passed to SomeFunction() is a local one. Your approach is pretty much the same as everyone else's that used pointers, worse than the reference approach, and it doesn't handle global pointers better or worse. There isn't anything special about global variables that needs special care. In all solutions(including yours) whether the variable is global or not doesn't matter. We're all saying the same thing here. We're talking about which mechanism the OP will use to communicate with SomeFunction(). As everyone else, you use the argument list and not a global variable to do that. Whether you *pass* into the argument list a global variable is completely irrelevant. Everybody else's solution have no problem in dealing with global variables(why would they had any problem,really?). It's just that in your first post, you seemed like you're saying to the OP that he *must* use a global pointer to solve some problem.

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathematix
*** Source Snippet Removed ***

The above can simply be fixed by simply having

*** Source Snippet Removed ***

Depending on what you want to do you can declare anInstance locally within main() or globally.

It's simple and it works if you use it carefully.


But... you're not fixing anything.

You explicitly mention "if you use it carefully", which, if applied to the original code we would re-write it and point the pointer at something.

Finally, the OPs problem concerned whether or not sending the address of a temporary to a function where the temporary was created in the call was okay. Your answer does not address this issue (IMO) as easily as mine. You seem to be adressing the issue of whether the pointer points at something valid. This issue is the reason references were created, so why not use them as the solution...

Again, I ask: How is your method better than mine ( useing references ).

Also, you are leaking memory...

Share this post


Link to post
Share on other sites
Quote:
Original post by Extrarius
Beware global variables. Once upon a time they were nothing special, but in these days of multi-core machines and corresponding shift towards multithreaded code, they're notable as the bane of thread-safe programming.
Yeah for sure!

I recently had to fix a program where someone had added a number of threads, and lots of globals, accessed by those threads, and some gotos just to top things off. I had to fix the crashing problems! I ended up taking out the threads, the globals, and the gotos, and the product is now stable again.
A simple Windows Timer and state-machine was all that was needed.

Share this post


Link to post
Share on other sites
This is the very reason why I stick to answering game programming forums away from gamedev.net. It's always a bad experience.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this