Obiekty stałe i konstruktor kopiujący

Obiekty stałe i konstruktor kopiujący
VH
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:14
0

Witam,

używam Qt Creator w Linuksie.

Chcę napisać przykładowy program, który będzie używał klasy ze zdefiniowanymi stałymi a, b, c oraz z konstruktorem kopiującym.

Napisałem taki oto program, który wywala błędy. Co zrobiłem nie tak? A może to błąd kompilatora (choć to szansa 1 na 1E6)?
Według mnie pierwszy z kodów jest logicznie bardziej poprawny. Ale żadna z wersji nie działa.

Kopiuj
#include <iostream>
using namespace std;
class Trojmian
{
    const double a, b, c;   double x;
public:
    Trojmian(double ax, double bx, double cx, double xx = 0.0):
        a(ax), b(bx), c(cx) { x = xx; }
    Trojmian(Trojmian &t) { x = t.x; }
    void wyswietl(void) { cout << a << b << c << x; }
};

int main(void)
{
  Trojmian t1(1, 0, -1), t2(1, 0, 0);
  t1 = t2;
  return 0;
}

Próbowałem też tak, ale błąd pojawiał się po wstawieniu instrukcji:

Kopiuj
t1 = t2; // a o tę instrukcję najbardziej mi chodzi
Kopiuj
#include <iostream>
using namespace std;
class Trojmian
{
    const double a, b, c;   double x;
public:
    Trojmian(double ax, double bx, double cx, double xx = 0.0):
        a(ax), b(bx), c(cx) { x = xx; }
    Trojmian(Trojmian &t): a(ax), b(bx), c(cx) { x = t.x; }
    void wyswietl(void) { cout << a << b << c << x; }
};

int main(void)
{
  Trojmian t1(1, 0, -1), t2(1, 0, 0);
  t1 = t2; // tutaj ta instrukcja powoduje błąd
  return 0;
}
Endrju
  • Rejestracja:około 22 lata
  • Ostatnio:ponad rok
1

Po co Ci ten const przy składnikach klasy? Przecież to nie ma sensu.

Żeby to działało musiałbyś przeciążyć operator=, ale nie możesz zmienić w nim stałych składników. Ta klasa nie ma więc sensu w tym kształcie.


"(...) otherwise, the behavior is undefined".
edytowany 1x, ostatnio: Endrju
AN
Jak nie jak tak ;P To znaczy nie masz racji że się nie da, ale masz całkowitą racje co do bezsensowności.
Endrju
Mam rację co do obu, patrz niżej. :-P
AN
Nie masz, parz niżej ;P
Endrju
Mam. Patrz niżej. :-P
AN
  • Rejestracja:około 12 lat
  • Ostatnio:około 12 lat
  • Postów:36
0

Poprawnie będzie tak:

Kopiuj
#include <iostream>
using namespace std;

class Trojmian
  {
   const double a, b, c;   double x;
   public:
   Trojmian(double ax,double bx,double cx,double xx=0.0):
        a(ax), b(bx), c(cx) { x = xx; }
   Trojmian(Trojmian &t): a(t.a), b(t.b), c(t.c) { x = t.x; }
   void wyswietl(void) { cout << a << b << c << x; }
   Trojmian &operator=(Trojmian &t)
     {
      const_cast<double&>(a)=t.a; // działa na 4-ch kompilatorach ale standard mówi - nieprzewidywalne zachowanie
      const_cast<double&>(b)=t.b; // jw
      const_cast<double&>(c)=t.c; // jw
      x=t.x;
      return *this;
     }
  };
 
int main(void)
  {
   Trojmian t1(1, 0, -1), t2(1, 0, 0);
   t1 = t2; // tutaj ta instrukcja powoduje błąd
   return 0;
  }

Nie jest to koszerne rozwiązanie, może zastanów się nad jednym z:

  1. zrezygnować z przypisywania: t1 = t2
  2. zrezygnować z const przy a,b,c
edytowany 1x, ostatnio: antimonium
Zobacz pozostałe 2 komentarze
Endrju
To nie jest żadna nowość w C++11, w poprzednich też tak było, po prostu podałem gdzie to jest w nowym (W 98 - 7.1.5.1.4). Nie ma znaczenia co jest const. Jakakolwiek próba zmodyfikowania czegokolwiek, co ma const jest UB a to właśnie robisz. Tego nie zmienia Twoje zdanie ani moje zdanie, to nie kwestia zdania, tak stanowi standard. const_cast ma kilka prawidłowych zastosowań, to akurat jest błędne. Poszukaj gdziekolwiek w Internecie, to nie jest niestety kwestia poglądów.
AN
No znalazłem, i rzeczywiście masz racje. Tylko jak to do cholery obsługuje kompilator ... Ja zawsze unikam podobnych rozwiązań, w żadnym poważnym projekcie nigdy nie użyłem const_cast'a więc nie czytałem tego dokładnie i traktowałem jak taką furtkę dla protez do kulawych projektów. A tu się okazuje że nigdy jej nie było :D Tylko że niestety działa.
Endrju
No i tutaj właśnie pokazuje się magia UB. One zazwyczaj działają zgodnie z oczekiwaniami! :-D Dlatego zgadzam się z Tobą - to co pokazałeś działa, mimo, że jest niepoprawne. Lepiej nie pokazywać tego nikomu, bo się nauczą. :-P Zawsze może się zdarzyć, że w rozbudowanym projekcie z agresywnymi optymalizacjami coś się skaszani.
Azarien
Działa, bo raz że referencja jest praktycznie zawsze przechowywana wewnętrznie jako wskaźnik, a dwa, że poza kontrolą ze strony kompilatora zwykle nie ma różnicy między zmienną const a nie-const. Ale to się może zemścić, bo teoretycznie coś co jest const może trafić do pamięci chronionej przed zapisem.
AN
Słabo sobie wyobrażam aby cześć (w sensie wybrane składowe) obiektu trafiła do pamięci chronionej. Co do "może zemścić" mści się każdy nieprzemyślany projekt, a to podpada pod tą kategorie.
VH
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:14
0

Faktycznie. Zapomniałem o const_cast. Ale zrezygnuję z tego kodu, bo jest niepoprawny.

To miało być w celach edukacyjnych (jestem nauczycielem). chciałem pokazać, że stałe można inicjalizować tylko za pomocą listy inicjalizacyjnej oraz jak działa konstruktor kopiujący (w jednym przykładzie).

A może ktoś ma lepszy pomysł na taki przykład?

AN
  • Rejestracja:około 12 lat
  • Ostatnio:około 12 lat
  • Postów:36
0

Jak musisz mieć stałą w obiekcie to operator = musi ją pominąć. Dobrym przykładem jest punkt który wewnątrz ma współrzędne x,y oraz stałą bool która mówi jedynie o sposobie zadania współrzędnych w konstruktorze oraz sposób ich wprowadzenia cin>> oraz wyświetlenia cout<<. Sposób w sensie kartezjańskie czy biegunowe. Owszem nie jest najlepszy przykład w sensie OOP ale ten przykład właśnie pokazuje sens pomijania takiej stałej.

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 9 godzin
1

Ale zrezygnuję z tego kodu, bo jest niepoprawny.
Wywal po prostu to const z pól klasy.
Bo stałe pola i operator przypisania wzajemnie sobie przeczą.

A może ktoś ma lepszy pomysł na taki przykład?
Jakaś klasa, która w konstruktorze przydziela pamięć przez new, a w destruktorze zwalnia.

Kopiuj
class Foobar
{
  int *ptr;
  public:
      Foobar() { ptr = new int[10]; }
      ~Foobar() { delete[] ptr; }
};

Zadanie: dorobić konstruktor kopiujący i operator przypisania.

Zadanie z gwiazdką: dorobić konstruktor przesuwający i operator przesunięcia.

edytowany 2x, ostatnio: Azarien
Endrju
Jeszcze przerobić to na copy and swap. ;-)
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:minuta
0

Cały problem polega na podejściu do nauczani języka programowania: tu macie taki feature języka, wymyślcie(lę) sobie do niego przykład.
Na tym forum pojawia się sporo kwiatków wynikającego z takiego podejście do nauczania i to jest jeden z nich.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22
Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 9 godzin
0

Co do tego kodu z Trojmianem, ja bym zrobił go tak:

Kopiuj
#include <iostream>
using namespace std;
class Trojmian
{
	double a, b, c, x;
    public:
	Trojmian(double ax, double bx, double cx, double xx = 0.0)
		: a(ax)
		, b(bx)
		, c(cx)
		, x(xx)
	{}

	friend ostream& operator << (ostream &out, const Trojmian &tr);
};

ostream& operator << (ostream &out, const Trojmian &tr)
{
	return out << ' ' << tr.a << ' ' << tr.b << ' ' << tr.c << ' ' << tr.x;
}
 
int main(void)
{
	Trojmian t1(1, 0, -1), t2(1, 0, 0);
	cout << t1 << endl;
	cout << t2 << endl;
  
	t1 = t2;
	cout << t1 << endl;
	cout << t2 << endl;
	return 0;
}

Jak widać konstruktora czy operatora kopiującego wcale w tym przypadku nie potrzeba.
Dodatkowo odpowiedni operator dla cout, zamiast metody wyswietl.

edytowany 1x, ostatnio: Azarien
AN
  • Rejestracja:około 12 lat
  • Ostatnio:około 12 lat
  • Postów:36
0

Wg mnie jeżeli już ma by klasa trójmian to nie powinna zawiera tego x, czyli coś na kształt:

Kopiuj
#include <iostream>
#include <iostream>
using namespace std;

struct Trojmian
  {
   double a, b, c;
   Trojmian(double a,double b,double c):a(a),b(b),c(c) {}
   ostream &prn(ostream &s)const;
   double operator()(double x) { return (a*x+b)*x+c; }
  };
inline ostream &operator<<(ostream &s,const Trojmian &T) { return T.prn(s); }

ostream &Trojmian::prn(ostream &s)const
  {
   s.setf(ios::showpos);
   if(a) { if(a==-1) s<<"-"; else if(a!=1) s<<a; else ; s<<"x^2"; }
   if(b) { if(b==-1) s<<"-"; else if(b!=1) s<<b; else if(a) s<<"+"; s<<b<<"x"; }
   if(c||((!a)&&(!b))) s<<c;   
   s.unsetf(ios::showpos);
   return s;
  }
 
int main(void)
  {
   Trojmian t1(1, 0, -1),t2(1, 0, 0);
   cout<<"t1="<<t1<<" t1(2)="<<t1(2)<<" t1(5)="<<t1(5)<<endl;
   t1.b=-2;
   cout<<"t1="<<t1<<" t1(2)="<<t1(2)<<" t1(5)="<<t1(5)<<endl;
   cout<<"t2="<<t2<<" t2(2)="<<t2(2)<<" t2(5)="<<t2(5)<<endl;
   t1 = t2;
   cout<<"t1="<<t1<<endl;
   cout<<"t2="<<t2<<endl;
   return 0;
  }

Ba wtedy nie boimy się dowolnych wartości a,b,c więc są jak najbardziej publiczne.
Wartość x podajemy jedynie dla obliczenia wartości trójmianu w konkretnym punkcie.

VH
  • Rejestracja:ponad 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:14
0
Azarien napisał(a):

Wywal po prostu to const z pól klasy.

I tak właśnie zrobiłem :-)

Dzięki wszystkim za odzew. Tym którzy najbardziej pomogli - kliknąłem.

Właściwie to przyszedł mi do głowy jeszcze jeden pomysł, ale to w osobnym wątku (chodzi o zweryfikowanie moich materiałów).

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.