Avatar billede mollevp Nybegynder
07. juli 2005 - 19:56 Der er 16 kommentarer og
1 løsning

Keyword efter funktions-navn

Hej alle jeg er ved at lære lidt c++, i den bog jeg læser er følgnede eksempel:

class Point
{ public:
Point( double= 0.0, double= 0.0); // default constructor
Point( const Point&); // copy constructor
~Point(); // destructor
Point& operator=( const Point&); // assignment operator
// accessor function
double x() const; // accessor function
double y() const;
string toString() const;

protected:
double _x, _y;
};


Efter double x() strå der const. Hvad betyder det?
Yderligere undrer det mig at De i eksemplet her viser assignmet constructoren - den bliver added af compileren ik? Og hvis der er nogen grund til hvorfor det kan være smart at selv definere disse "ekstra" constuctorer - hvad er det så?

Mvh Morten
Avatar billede bertelbrander Novice
07. juli 2005 - 20:08 #1
const efter funktions navn betyder at funktionen ikke modificerer det object den er kaldt på, og at funktionen kan kaldes på const objecter.

Hvis kompileren kan lave en assignment operator er der som regel ikke nogen grund til at lave en selv, men i nogle tilfælde er du nødt til at lave en selv. Hvis class'en indeholder en pointer bør man altid lave en assignment operator.
Avatar billede mollevp Nybegynder
07. juli 2005 - 20:37 #2
ok, det giver mening i eksemplet bruger de også kun const funktionerne til at returnere værdier..

Til det sidste, kan du komme med eksemple hvor man bør lave en assignment operator (og måske uddybe lidt hvorfor man bør det?)
Avatar billede bertelbrander Novice
07. juli 2005 - 20:56 #3
#include <iostream>
#include <stdlib.h>
#include <string.h>

class StringClass
{
public:
  StringClass(const char *aStr)
  {
      Str = new char [strlen(aStr) + 1];
      strcpy(Str, aStr);
  }
  ~StringClass()
  {
      delete [] Str;
  }
  char *Str;
};

std::ostream &operator << (std::ostream &os, const StringClass aStr)
{
  os << aStr.Str;
  return os;
}

int main()
{
  StringClass S1("Hello");
  StringClass S2("World");
  std::cout  << S1 << " " << S2 << std::endl;
  S1 = S2; // UPS
}

Problemet er at efter linien "S1 = S2;" vil både S1 og S2 pege på den samme streng, så nå man nedlægger S1 og S2 (i destructoren) vil de begge forsøge at delete det samme memory, og ingen delete'er det S2 pegede på.

En rigtig assignment operator kunne se sådan ud:
  StringClass &operator = (const StringClass &rhs)
  {
      delete [] Str;
      Str = new char [strlen(rhs.Str) + 1];
      strcpy(Str, rhs.Str);
      return *this;
  }

Og så skal man også lave en copy constructor og en default constructor!
Avatar billede mollevp Nybegynder
07. juli 2005 - 21:15 #4
ok, vil lige prøve dit eksempel.. et lille ekstra spm: hvorfor bruger du ikke using namespace std; istedet for at hele tiden skulle skrive std::?
Avatar billede bertelbrander Novice
07. juli 2005 - 21:26 #5
Det er mest et spørgsmål om smag og behag.
Hvis man skriver "using namespace std;" er der en lang række navne man ikke kan bruge.
Ved at skrive std:: foran er der ikke nogen der kan være i tvivl om at delen efter er en del af standard biblioteket.
Man kan også skrive: using std::cout; Så er det kun cout det gælder for.

Der er en fejl i min << operator, det skal være:
  StringClass &operator = (const StringClass &rhs)
  {
      delete [] Str;
      Str = new char [strlen(rhs.Str) + 1];
      strcpy(Str, rhs.Str);
      return *this;
  }

Ellers vil den vise hvorfor der skal være en copy constructor!
Avatar billede stormy Nybegynder
07. juli 2005 - 22:58 #6
Der er glemt en vigtig detalje ved den viste assignment operator,
nemlig at sikre at den er veldefineret, hvis et objekt tildeles sig selv.

Det kan virke pedantisk, og hvem vil dog nogensinde gøre noget sådant. Men når
først et C++ program vokser i størrelse, og referencer til objekter bliver brugt -
kan denne situation sagtens optræde... Og kan være et mareridt at debugge sig frem til.

Derfor er en bedre implementation af operator=, denne :

  StringClass &operator = (const StringClass &rhs)
  {
      if( this == &rhs )
          return *this;
      delete [] Str;
      Str = new char [strlen(rhs.Str) + 1];
      strcpy(Str, rhs.Str);
      return *this;
  }
Avatar billede bertelbrander Novice
07. juli 2005 - 23:01 #7
Ja, det er en forglemmelse, der skal naturligvis checkes for self-assignments.
Avatar billede mollevp Nybegynder
08. juli 2005 - 11:57 #8
Hej begge to - sry jeg var lidt langsom til at få prøvet det af..
Bertel jeg har prøvet dit eksempel, men mangle lige lidt inden forståelsen er helt på plads..

Jeg har lavet det lidt anderledes - men det er vis kun et spm. om fremgangsmåde:

// Bertel der viser hvorfor man i nogel tilfaelde skal lave assignment
// operatorer
#include <cstring>
#include <iostream>
using namespace std;

class StringClass
{
public:
    StringClass(char *aStr);
    ~StringClass();
    void printAddr();
    char *str;
};

StringClass::StringClass(char *aStr)
{
    str = new char[strlen(aStr)+1];
    strcpy(str, aStr);
}
StringClass::~StringClass()
{
    delete [] str;
}
void StringClass::printAddr()
{
    cout << "Adresse: " << &str << endl;
}


ostream &operator << (ostream &os, StringClass aStr)
{
    os << aStr.str;
    return os;
}

int main()
{
    StringClass S1("Hello");
    StringClass S2("World");
   
    cout << S1 << " " << S2 << endl;
   
    S1.printAddr();
    S2.printAddr();
 
    S1 = S2;
   
    cout << S1 << " " << S2 << endl;
   
    S1.printAddr();
    S2.printAddr();
   
}

Output her er så:

Hello World
Adresse: 0xbffff150
Adresse: 0xbffff140
Hd Hd
Adresse: 0xbffff150
Adresse: 0xbffff140

Tilsyneladende kommer S2 altså ikke til at pege på S1? Kan man ikke sige at hvis jeg selv laver en klasse - så skal jeg selv overloade assignment operatoren hvis jeg vil have en = til at funke (samt  vel oxo alle andre operatorer)..?
Avatar billede mollevp Nybegynder
08. juli 2005 - 11:59 #9
ligesom vi oxo overloader std::ostream operatoren << så den kan finde ud at bruge vores klasse..
Avatar billede stormy Nybegynder
08. juli 2005 - 19:10 #10
Ok - nu begynder det at blive lidt kompliceret, så jeg vil prøve at starte med at forklare lidt om hvad C++ laver bagved scenen, når du laver en ny klasse.

Når du skriver :

class StringClass
{
public:
    StringClass(char *aStr);
    ~StringClass();
    void printAddr();
    char *str;
};

Er C++ compileren så hjælpsom, at læse dette som :

class StringClass
{
public:
    StringClass(char *aStr);
    StringClass(const StringClass& rhs); // Copy construktor - usynlig tilføjelse
    StringClass% operator=(const StringClass& rhs); // Assignment operator - usynlig tilføjelse
    ~StringClass();
    void printAddr();
    char *str;
};

Og oprette disse usynlige funktioner for dig, hvis der er behov for dem. Og det er der i det eksempel du har lavet !

Når du skriver :

cout << S1 << " " << S2 << endl;

Kalder du din std::ostream operator<<, der er erklæret som :

ostream &operator << (ostream &os, StringClass aStr)

Her burde du skrive :

ostream &operator << (ostream &os, StringClass &aStr)

Men det, at du ikke har gjort det, gør at der bliver oprettet en ny lokal instans af din StringClass - vha. den usynlige copy constructor. Den sørger så for at lave en kopi af indholdet af klassen, MEN (dette er det store MEN, mere om det senere) sørger ikke for at at lave en kopi af det der bliver peget på - den kopierer blot pegeren.

Når denne lokale kopi så bliver nedlagt - vil den hukommelse du har allokeret blive frigivet. Hvilket betyder at det oprindelige objekt nu peger på noget udefineret.

Når du så laver en tildeling, sker det samme - men i dette tilfælde peger de allerede på noget udefineret, så anden gang du skriver ud er det udefineret hvad der står. (Hd i dit eksempel).

For at summere op :

Hvis du ikke laver dem, vil C++ automatisk lave en copy constructor og en assignment operator for dig. Disse laver en primitiv kopi af indholdet af det data der er i din klasse, hvilket betyder at når man arbejder med pointere, så mister man nogen som helst mulighed for at vide hvornår man egentlig skal frigive det allokerede stykke hukommelse, som de forskellige kopier arbejder på.

Derfor :

Så snart en klasse har pointere, skal man enten

1) Selv skrive copy constructor og assignment operator, således at man har styr på at hver klasse har sit eget hukommelsesområde - eller de i det mindste kan finde ud af at dele det samme på en fornuftig måde.

F.eks. bruger en tæller til at holde styr på hvor mange steder fra det samme stykke hukommelse er kendt - således at sidste mand lukker og slukker. ( Det sidste objekt der bruger dette stykke hukommelse, frigiver det når det bliver destrueret. De andre kopier tæller blot ned, når de bliver destrueret. )

2) Sørge for at compileren ikke skriver en copy constructor og en assignment operator.
Dette gøres ved at erklære dem private, og aldrig definere dem. Så vil man få en fejlbesked, hvis man laver noget kode der ellers ville bruge dem (som nu netop i forbindelse med din version af std::ostream operator<< ).

Det vil se således ud :

class StringClass
{
public:
    StringClass(char *aStr);
tilføjelse
    ~StringClass();
    void printAddr();
    char *str;
private:
    StringClass(const StringClass& rhs); // Copy construktor - aldrig defineret!
    StringClass% operator=(const StringClass& rhs); // Assignment operator - aldrig defineret!
};


Håber at dette ikke blot gør din forvirring større - det er konceptuelt relativt hårdt C++ stof !

P. S.

Grunden til at : Tilsyneladende kommer S2 altså ikke til at pege på S1?
Er at det du skriver ud, er ikke hvad str peger på, men adressen pointervariablen i klassen.. Da S1 og S2 er to forskellige klasser, er dette også to forskellige adresser. Det er indholdet på denne adresse, der har ændret sig.
De du nok forsøger at skrive ud, er :

void StringClass::printAddr()
{
    cout << "Adresse: " << static_cast<void *>(str) << endl;
}

- Egentlig er det str selv, men da <<, vil skrive en char * ud, som det den peger på,
er det nødvendigt at "snyde", ved at caste den til en void *.
Avatar billede mollevp Nybegynder
08. juli 2005 - 19:37 #11
Ok.. mange tak for det lange svar - jeg tygger lige lidt på den :)
Avatar billede stormy Nybegynder
08. juli 2005 - 19:54 #12
Læs StringClass% som StringClass& - Slåfejl.
Avatar billede stormy Nybegynder
08. juli 2005 - 20:53 #13
Mit tidligere svar, tog udgangspunkt i den kode du (og bertelbrander) havde lavet -
men selve kernen i problemet kan forklares således. (Samme koncept, anden fremgangsmåde).

1) Hvad er copy construktor og assignment operator, og hvornår bruges de.
(Har du allerede rimeligt styr på).
2) Hvad laver de versioner af copy construktor og assignment operator, som
compileren automatisk laver.
3) Hvorfor dette er et problem i forbindelse med hukommelsesallokering og
pointere.
4) Løsning på 3.



1) Hvad er copy construktor og assignment operator, og hvornår bruges de :

En assignment operator, er relativ simpel, den bliver brugt hver gang, et opjekt
tildeles et andet. Eksempel :

S1 = S2;

Den erklæres som : StringClass& operator(const StringClass &rhs);

En copy construktor, bruges når der oprettes et nyt objekt, hvis indhold kommer
fra et allerede eksisterende. Eksempler på dette :

Eksempel 1:

StringClass newString(existingString);

Eksempel 2:

Men også i forbindelse med funktionskald :

void doSomething( StringClass localString )
{ ... }

Når doSomething bliver kaldt, bliver copy consruktoren brugt for oprette
localString. F.eks :

doSomething(existingString); // <- Laver en lokal kopi (localString)

Den erklæres som : StringClass(const StringClass &rhs);


2) Hvad laver de versioner af copy construktor og assignment operator, som
compileren automatisk laver.

De foretager en tildeling til mål-objektets variabel udfra kilde-objektets
variabel.

Hvilket betyder at for en simpel klasse, som f.eks. class point vil en compiler
genereret assignment operator se således ud :

Point& operator(const Point& rhs)
{
  _x = rhs._x;
  _y = rhs._y;
  return *this;
}

Så i mange sammenhænge er det faktisk en god hjælp at compileren automatisk
(og usynligt) laver disse funktioner for en!


3) Hvorfor dette er et problem i forbindelse med hukommelsesallokering og
pointere.

Fordi hvis et objekt har en constructor, der allokerer noget hukommelse, og som
frigiver denne hukommelse, når det nedlagt. Vil de versioner som copy
construktor og assignment operator, som compileren lave ikke vide hvordan de
skal holde styr på dette hukommelse. Det er her at StringClass kommer ind som et
godt eksempel :

Compiler genereret version af operator= :

StringClass &operator = (const StringClass &rhs)
{
      Str = rhs.Str;
      return *this;
}

Men her bliver this->str overskrevet, uden at det allokerede hukommelse bliver
frigivet. Samtidig vil både this->Str og rhs.Str nu pege på de samme hukommelse,
som de begge vil forsøge at frigive når de bliver destrueret. At frigive de
samme stykke hukommelse flere gange er klassisk kogebogs opskrift på at lave
kode der går ned !

bertelbranders version af operator= (med min tilføjelse) :

StringClass &operator = (const StringClass &rhs)
{
      if( this == &rhs )
          return *this;
      delete [] Str;
      Str = new char [strlen(rhs.Str) + 1];
      strcpy(Str, rhs.Str);
      return *this;
}

Først checker man for self-assignment, hvor man så intet gør.
Hvis det er to forskellige objekter, så frigiver man det stykke hukommelse som
objektet tidligere brugte. Og allokerer et nyt område, som dette objekt kan pege
på (og kopierer indholdet). Dette sikrer at både this->Str og rhs.Str peger på
hvert sit stykke hukommelse, og at de kan frigive det uafhængigt af hinanden.

4) Løsning på 3.

Bertelbrander viste en løsning, for at forklare hvorfor man nogle gange gerne
ville lave sin egen assignment operator, istedet for den compileren laver.

Alternativt kan man sikre at de aldrig bliver kaldt, som jeg forklarer i mit
tidligere svar.
Avatar billede bertelbrander Novice
09. juli 2005 - 00:04 #14
Problemet med min code fra "07/07-2005 20:56:31" er at der mangler et & i andet argument til << operatornen. Derved bliver objectet der skal udskrevet kopieret, og copy constructoren bliver kaldt. Men da den copy constructor som kompileren laver fordi jeg ikke lavede en, ikke virker, vil objectet være ødelagt efter udskrivning.

Problemet løses ved at tilføje en & til andet argument, derved overføres en reference til objektet, og der sker ingen kopiering. Man kan også lave en rigtig copy constructor, men der er normalt ingen grund til at kopiere et object for at udskrive det.
Avatar billede mollevp Nybegynder
19. juli 2005 - 02:21 #15
Tak for jeres gode forklaringer .. smid et par svar..

MVH Morten
Avatar billede mollevp Nybegynder
03. august 2005 - 01:04 #16
Ingen der vil have point?
Avatar billede stormy Nybegynder
10. august 2005 - 09:37 #17
Well - Bertelbrander samler ikke point, det gør jeg heller ikke.. Men lad os få lukket spørgsmålet.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester