Toon posts:

[C++] Probleem ivm. copy constructor / returnen object

Pagina: 1
Acties:
  • 128 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Hoi

Ik had een klein test programma gemaakt, om de werking van de copy constructor te zien bij het returnen van een object. Mijn programma:

objret.h:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CObjReturn
{
private:
    int m_int;
public:
    CObjReturn(int input = 10)
    {
        cout << "C! ";
        m_int = input;
    }
    CObjReturn(const CObjReturn & objectBron)
    {
        cout << "CC! ";
        m_int = objectBron.returnWaarde();
    }
    int returnWaarde(void) const { return m_int; }
};


main.cpp:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream.h>
#include "objret.h"

CObjReturn returnObject(void);

void main (void)
{
    CObjReturn object1;
    object1 = returnObject();
    cout << object1.returnWaarde();

    cout << "\n";

    CObjReturn object2 = returnObject();
    cout << object2.returnWaarde();
}

CObjReturn returnObject(void)
{
    CObjReturn temp; /* lokaal */
    
    return temp;
}
Ik krijg volgende output:
C! C! CC! 10
C! CC! CC! 10

De output van lijn 1 van de output snap ik: eerst wordt object1 aangemaakt (constr.), dan wordt object temp aangemaakt (constr.) en dan wordt temp gekopieerd naar object1 (copy constr.).
De output van lijn 2 snap ik daarentegen niet. Waarvoor staat die laatste CC! (copy constructor)? Waarvoor staat die extra CC!? Er wordt toch maar 1 keer gekopieerd?

Het resultaat is in beide gevallen hetzelfde (wat het zou moeten zijn natuurlijk), maar ik snap de manier van werken in situatie 2 dus niet.

Misschien kan iemand mij helpen ;)

[ Voor 0% gewijzigd door Verwijderd op 28-09-2002 13:27 . Reden: foute benamingen, corrected ]


  • Orphix
  • Registratie: Februari 2000
  • Niet online
Het verschil zit 'm in initialisatie of niet. Voeg eens de volgende code toe:
C++:
1
2
3
4
5
6
CObjReturn& operator=(const CObjReturn& x)
{
  cout << "assignment ";
  m_int = x.returnWaarde();
  return *this;
}

En bekijk dan de output eens.

Verwijderd

Topicstarter
Dan krijg ik als output:
code:
1
2
C! C! CC! assignment 10
C! CC! CC! 10
Ik snap niet goed wat je wil zeggen? In de tweede situatie wordt 10 niet "toegekend", maar "gekopieerd". Maar waar vandaan dan?

alvast bedankt

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Je compiler optimaliseert het niet. Dus hij maakt een copy na je call naar returnObject() en dan initialiseert hij object2 met die return waarde. Een compiler mag (moet? weet ik ff niet uit mn hoofd) deze stap optimaliseren en het in een keer doen. Maar de jouwe doet dat dus niet. Welke compiler gebruik je? Visual C++ 6 geeft bv als output "C! CC! 10". Misschien moet je iets van -O3 als optie meegeven.

Oh, en gebruik aub <iostream> en niet <iostream.h> dat is een oude header. En dan moet je na die include nog zetten "using std::cout;"

(en het is int main(), niet void main()...)


Je hoeft overigens niet die returnWaarde() te gebruiken, je kan je CC ook zo doen:

CObjReturn(const CObjReturn & objectBron) {
cout << "CC! ";
m_int = objectBron.m_int;
}

Verwijderd

Topicstarter
Ok, nu snap ik het ;)

PS: Jup, je zal het wel gemerkt hebben, ben nog maar een weekje bezig met C++, ik zal vanaf nu jou 2 tips volgen! thanks Zoijar

PS2: Ik gebruik visual c++ 6 ja, goed geraden :)
Je hoeft overigens niet die returnWaarde() te gebruiken, je kan je CC ook zo doen:

CObjReturn(const CObjReturn & objectBron) {
cout << "CC! ";
m_int = objectBron.m_int;
m_int is toch een private member in objectBron? Ik moet hem toch bereiken via een public member function? Of zie ik het mis?

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Verwijderd schreef op 28 september 2002 @ 13:51:
Ok, nu snap ik het ;)

PS: Jup, je zal het wel gemerkt hebben, ben nog maar een weekje bezig met C++, ik zal vanaf nu jou 2 tips volgen! thanks Zoijar


PS2: Ik gebruik visual c++ 6 ja, goed geraden :)
Huh? :? Bij mij in VC6 krijg ik als output niet die tweede "CC". Heb je service pack 5 geinstalleerd? Misschien moet je dat ff doen, basis vc6 is nogal buggy.

http://msdn.microsoft.com...es/sp/vs6/sp5/default.asp
m_int is toch een private member in objectBron? Ik moet hem toch bereiken via een public member function? Of zie ik het mis?
Dat kan omdat je in het object zelf zit. Die copy constructor is een member functie van het object, en mag dus gewoon bij alle eigenschappen van de !class!. Dus niet alleen specifiek zn eigen object, maar ook elk ander object van hetzelfde type. Als je er over denkt is het wel logisch, van buiten kan er niemand bij, maar jij zit al in die class dus kan je er wel bij.

Verwijderd

Topicstarter
Zoijar schreef op 28 september 2002 @ 13:58:
[...]

Huh? :? Bij mij in VC6 krijg ik als output niet die tweede "CC". Heb je service pack 5 geinstalleerd? Misschien moet je dat ff doen, basis vc6 is nogal buggy.

http://msdn.microsoft.com...es/sp/vs6/sp5/default.asp
Ik gebruik VC6 ja, maar die service pack heb ik nog niet. Zal hem nu installen!
[...]


Dat kan omdat je in het object zelf zit. Die copy constructor is een member functie van het object, en mag dus gewoon bij alle eigenschappen van de !class!. Dus niet alleen specifiek zn eigen object, maar ook elk ander object van hetzelfde type. Als je er over denkt is het wel logisch, van buiten kan er niemand bij, maar jij zit al in die class dus kan je er wel bij.
Ach ja! tuurlijk! ;) Ik zit niet in de main, maar in de class zelf. Thanks voor de tip :)

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoijar schreef op 28 september 2002 @ 13:58:
Dat kan omdat je in het object zelf zit. Die copy constructor is een member functie van het object, en mag dus gewoon bij alle eigenschappen van de !class!. Dus niet alleen specifiek zn eigen object, maar ook elk ander object van hetzelfde type. Als je er over denkt is het wel logisch, van buiten kan er niemand bij, maar jij zit al in die class dus kan je er wel bij.


even een aanvullig voor DiEana: let er wel op dat dat weer niet geld voor protected members van een base class. Als je een subclass van een andere class maakt die protected members heeft, dan kan die subclass alleen bij de base members van z'n eigen instantie, niet bij die van een andere van hetzelfde type

dit klopt natuurlijk niet, stupid me |:(
Het geldt alleen als je een base class hebt en daar 2 verschillende afgeleiden ervan, dan kan de ene niet bij de protected members van de andere, ook al hebben ze dezelfde baseclass

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
.oisyn schreef op 28 september 2002 @ 14:22:

[...]


even een aanvullig voor DiEana: let er wel op dat dat weer niet geld voor protected members van een base class. Als je een subclass van een andere class maakt die protected members heeft, dan kan die subclass alleen bij de base members van z'n eigen instantie, niet bij die van een andere van hetzelfde type :)
Het principe van inheritance/subclasses heb ik nog niet geleerd (ben nog maar 2 dagen bezig), maar ik zal je tip onthouden! ;) thanks!

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

mmja zie mijn edit ;)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
Nu wordt het wel verwarrend ;) Ik kom er wel op terug van zodra ik dat principe geleerd heb. Thanks ;)


Nu ik eraan denk, eigenlijk is dat geen goede methode, voor het returnen van objecten! Reden: er wordt altijd een kopietje gemaakt, wat extra tijd kost.

In principe zou je dat doen met pointers/returnen van adreswaarde. Maar in C++ (ik kom dus van C) heb je, heb ik gelezen, ook een een "reference"-principe, dus ik wou het zo eens oefenen.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ref & f1 (void);
ref f2 (void);

void main(void)
{
    ref & obj1 = f1();

    cout << "\n";

    ref & obj2 = f2();
}

ref & f1 (void)
{
    ref * temp = new ref;
    return *temp;
}

ref f2 (void)
{
    ref * temp = new ref;
    return *temp;
}
Klopt het volgende wat ik hier zeg:

· Bij f1 wordt er een referentie gemaakt naar een dynamisch object.
· Bij f2 wordt er een referentie gemaakt naar een kopie van een dynamisch object. (hier wordt de copy constructor dus nodeloos gebruikt + je blijft met een dynamisch object achter, waar je niet meer aan kunt, want je bent je pointer kwijt)

Of zie ik het hier (ook) mis?

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

de implementatie van een reference is eigenlijk hetzelfde als een pointer ja
Alleen vergeet niet dat je nu memory leaks krijgt: de destructor van de 2 temp objecten wordt nooit aangeroepen, en het geheugen wordt nooit vrijgegeven

Overigens optimalizeren veel compilers het kopieren van objecten: ze geven als onzichtbare parameter meestal het adres mee waar het resultaat in moet komen te staan. Dit stukje code:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class C
{
public:
    static const int ARRAY_SIZE = 16;
    int array[ARRAY_SIZE];

    C ()
    {
        fill (array, array + ARRAY_SIZE, 0);
        cout << "C::C ()" << endl;
    }

    C (const C & c)
    {
        copy (c.array, c.array + ARRAY_SIZE, array);
        cout << "C::C (const C &)" << endl;
    }

    ~C ()
    {
        cout << "C::~C ()" << endl;
    }
};

C func ()
{
    return C ();
}


int main ()
{
    {
        C c = func ();
    }

    getch ();

    return 0;
}


geeft in MSVC7 als output
code:
1
2
C::C ()
C::~C ()

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
.oisyn schreef op 28 september 2002 @ 14:54:
de implementatie van een reference is eigenlijk hetzelfde als een pointer ja
Alleen vergeet niet dat je nu memory leaks krijgt: de destructor van de 2 temp objecten wordt nooit aangeroepen, en het geheugen wordt nooit vrijgegeven
In het eerste geval toch niet, want ik blijf toch het dynamisch object gebruiken, maar dan via de referentie? In het tweede geval (volgens mij) inderdaad wel ja, want daar maak ik een kopie van het dynamisch object, en ga daar een referentie aanhangen (dus het origineel, dynamisch object blijft bestaan, maar ik kan er niet meer aan, en kan het dus ook "nooit" meer wissen?).
Overigens optimalizeren veel compilers het kopieren van objecten: ze geven als onzichtbare parameter meestal het adres mee waar het resultaat in moet komen te staan. Dit stukje code:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class C
{
public:
    static const int ARRAY_SIZE = 16;
    int array[ARRAY_SIZE];

    C ()
    {
        fill (array, array + ARRAY_SIZE, 0);
        cout << "C::C ()" << endl;
    }

    C (const C & c)
    {
        copy (c.array, c.array + ARRAY_SIZE, array);
        cout << "C::C (const C &)" << endl;
    }

    ~C ()
    {
        cout << "C::~C ()";
    }
};

C func ()
{
    return C ();
}


int main ()
{
    {
        C c = func ();
    }

    getch ();

    return 0;
}


geeft in MSVC7 als output
code:
1
2
C::C ()
C::~C ()
Jup, snap ik pêêêrfect ;)

Verwijderd

Topicstarter
Heb het even getest, hij crasht waar '// delete temp2' staat. Dus het klopt wat ik wou zeggen?
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ref & f1 (void);
ref f2 (void);

void main(void)
{
    ref & obj1 = f1();
    ref * temp = &obj1;
    delete temp;

    cout << "\n";

    ref & obj2 = f2();
    ref * temp2 = &obj2;
    //delete temp2;

}

ref & f1 (void)
{
    ref * temp = new ref;
    return *temp;
}

ref f2 (void)
{
    ref * temp = new ref;
    return *temp;
}

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

nee :)
hij kan temp niet deleten omdat het adres van obj2 niet hetzelfde is als die van temp die je aanmaakt in f2 ()
Je geeft daar tenslotte geen reference of pointer terug, maar een kopie. Hij probeert de kopie dus te deleten, en ja, dat gaat niet, want die kopie staat op de stack en niet op de heap :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
.oisyn schreef op 28 september 2002 @ 15:14:
nee :)
hij kan temp niet deleten omdat het adres van obj2 niet hetzelfde is als die van temp die je aanmaakt in f2 ()
Je geeft daar tenslotte geen reference of pointer terug, maar een kopie. Hij probeert de kopie dus te deleten, en ja, dat gaat niet, want die kopie staat op de stack en niet op de heap :)
Dat zei ik toch? :> :? :/

Ik citeer mezelf :)
Klopt het volgende wat ik hier zeg:

· Bij f1 wordt er een referentie gemaakt naar een dynamisch object.
· Bij f2 wordt er een referentie gemaakt naar een kopie van een dynamisch object. (hier wordt de copy constructor dus nodeloos gebruikt + je blijft met een dynamisch object achter, waar je niet meer aan kunt, want je bent je pointer kwijt)
en
In het eerste geval toch niet, want ik blijf toch het dynamisch object gebruiken, maar dan via de referentie? In het tweede geval (volgens mij) inderdaad wel ja, want daar maak ik een kopie van het dynamisch object, en ga daar een referentie aanhangen (dus het origineel, dynamisch object blijft bestaan, maar ik kan er niet meer aan, en kan het dus ook "nooit" meer wissen?).

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op 28 september 2002 @ 14:54:
de implementatie van een reference is eigenlijk hetzelfde als een pointer ja
Let wel, een reference kan niet NULL zijn, een pointer wel. Je moet een reference dus altijd met een geldige waarde initialiseren.

Verder mag je geen reference naar een reference hebben, wel een pointer naar een pointer. Dit is bij templates soms nog wel eens een probleem (bv. vector<object&>)

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

[nohtml]
Zoijar schreef op 28 september 2002 @ 15:19:
Let wel, een reference kan niet NULL zijn, een pointer wel. Je moet een reference dus altijd met een geldige waarde initialiseren
ik bedoelde 'onder water'. Als jij een reference als parameter meestuurt dan wordt het adres meegestuurd, net als bij een pointer. Bovendien kun je m best NULL maken:
C++:
1
2
3
int * i = NULL;
...
int & ref = *i;


(uiteraard kan dat ook in 1 regel, maar dan geef je duidelijk aan dat je het forceert, dus vandaar met een stukje andere code ertussen zodat het niet echt duidelijk meer is ;))

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op 28 september 2002 @ 15:15:
[...]


Dat zei ik toch? :> :? :/

Ik citeer mezelf :)

[...]
en
[...]


mmja dan begrijp ik je post verkeerd. In het eerste geval kun je m idd nog deleten, maar je moet er wel aan denken dat je m delete, anders krijg je memory leaks. En in het 2e geval ben je de pointer gewoon kwijt, daar kun je m dus idd niet eens deleten

Maar je reden van je post was efficientie. En het alloceren van geheugen gaat over het algemeen langzamer dan even een object constructen/kopieren (ligt er natuurlijk ook maar net aan wat voor code er in de constructors staat enzo ;))

ikzelf kies er altijd voor om nooit grote objecten te returnen vanuit een functie. Ik maak dan meestal een extra parameter aan waar het resultaat in moet komen te staan:

C++:
1
2
3
4
void multiply (Matrix & dest, const Matrix & m1, const Matrix & m2)
{
    // en hier iets wat "dest = m1 * m2" uitvoert
}


(hoewel ik de functie multiply dan wel als member definieer van de klasse Matrix, en dan vervalt dest, want dat wordt dan gewoon this, zodat ik kan schrijven: dest.multiply (m1, m2);)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Topicstarter
.oisyn schreef op 28 september 2002 @ 15:32:

[...]


mmja dan begrijp ik je post verkeerd. In het eerste geval kun je m idd nog deleten, maar je moet er wel aan denken dat je m delete, anders krijg je memory leaks. En in het 2e geval ben je de pointer gewoon kwijt, daar kun je m dus idd niet eens deleten
Ok we zijn het eens! :)
Maar je reden van je post was efficientie. En het alloceren van geheugen gaat over het algemeen langzamer dan even een object constructen/kopieren (ligt er natuurlijk ook maar net aan wat voor code er in de constructors staat enzo ;))
Hmm, dat wist ik niet, ik dacht dat alloceren sneller ging, dan aanmaken+kopiëren.
ikzelf kies er altijd voor om nooit grote objecten te returnen vanuit een functie. Ik maak dan meestal een extra parameter aan waar het resultaat in moet komen te staan:..
Zo deed ik het meestal in C ook, als ik met structures werkte. Echt grote programmas in C++ met objecten heb ik nog niet gemaakt, maar waarschijnlijk ga ik het ook zo verder doen (dus zowel bron als doel by reference (pointer) meegeven). De reden dat ik nu bezig ben met het returnen van objecten, is gewoon omdat ik daar nu over aan het leren ben, en dus niet dat ik de intentie heb om het zelf te gebruiken :D

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Beetje off-topic, maar moet kunnen...
.oisyn schreef op 28 september 2002 @ 15:26:
[nohtml]
[...]


ik bedoelde 'onder water'. Als jij een reference als parameter meestuurt dan wordt het adres meegestuurd, net als bij een pointer. Bovendien kun je m best NULL maken:
C++:
1
2
3
int * i = NULL;
...
int & ref = *i;


(uiteraard kan dat ook in 1 regel, maar dan geef je duidelijk aan dat je het forceert, dus vandaar met een stukje andere code ertussen zodat het niet echt duidelijk meer is ;))
Wat jij doet mag eigenlijk niet. Het is net zoiets als "force cast", waarbij je een cast afdwingt door source en destination in een union te zetten, de union vult met source en dan destination returned. Je kan op die manier een float naar een functie pointer casten, iets wat ook eigenlijk niet mag.
There shall be no references to references, no arrays of references, and no pointers to references. The declaration
of a reference shall contain an initializer (8.5.3) except when the declaration contains an explicit
extern specifier (7.1.1), is a class member (9.2) declaration within a class declaration, or is the declaration
of a parameter or a return type (8.3.5); see 3.1. A reference shall be initialized to refer to a valid object
or function. (Note: in particular, a null reference cannot exist in a welldefined
program, because the only
way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer,
which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bitfield.)

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

wat is er mis met het maken van een reference door een pointer te dereferencen :?
Dat is sowieso wat er regelmatig met this gebeurd, hoewel ie daar nooit NULL zal zijn

Dat dat gebeurd terwijl de pointer NULL is komt idd gewoon door slecht design, en daar zul je mij ook niet over horen :Y)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
.oisyn schreef op 28 september 2002 @ 15:26:
[nohtml]
[...]


ik bedoelde 'onder water'. Als jij een reference als parameter meestuurt dan wordt het adres meegestuurd, net als bij een pointer. Bovendien kun je m best NULL maken:
C++:
1
2
3
int * i = NULL;
...
int & ref = *i;


(uiteraard kan dat ook in 1 regel, maar dan geef je duidelijk aan dat je het forceert, dus vandaar met een stukje andere code ertussen zodat het niet echt duidelijk meer is ;))
Dat werkt dus niet zoals je denkt. Je zou verwahten dat &ref==NULL, maar op bv GCC3.0 werkt dat niet. GCC 3.0 zal de volgende code dus wegoptimaliseren:
code:
1
2
3
4
if ( &ref == NULL ) 
{
  std::cout << "Null reference" << std::endl;
}
(Niet zlef getest, overigens)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

MSalters schreef op 28 september 2002 @ 17:29:
[...]

Dat werkt dus niet zoals je denkt. Je zou verwahten dat &ref==NULL, maar op bv GCC3.0 werkt dat niet. GCC 3.0 zal de volgende code dus wegoptimaliseren:
code:
1
2
3
4
if ( &ref == NULL ) 
{
  std::cout << "Null reference" << std::endl;
}
(Niet zlef getest, overigens)


hoe bedoel je, het werkt niet?

Bedoel je dat, door een in principe foute optimalisatie, de code in de if nooit aangeroepen wordt, ook al is &ref daadwerkelijk wel NULL?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Nog een keer dan: "(Note: in particular, a null reference cannot exist in a welldefined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior.)"

"Undefined behavior" betekend dat er letterlijk van alles kan gebeuren. Je compiler kan het dus gewoon negeren, of het lijkt te werken, of het crashed je hele systeem. Een vergelijking of een reference NULL is slaat dus nergens op, want als je een null-reference verkrijgt treed er undefined behavior op en hoeft die ref dus helemaal niet daadwerkelijk NULL te zijn, hij kan overal naar wijzen en het kan per compiler nog verschillen.
Er is dus ook geen "foute" optimalisatie oid, je kan als compiler niets "fout" doen als het om een null ref gaat. Alles wat je doet is "goed", namelijk undefined.

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

dat bedoelde ie dus :Y)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1