Προς το περιεχόμενο

Eπειγον - Εργασια C++


mgt01

Προτεινόμενες αναρτήσεις

Δημοσ.

Εγχειριδιο C++ (Jesse Liberty) Εκδοτης Γκιουρδας

 

Απιστευτο, εχω ακριβως το ιδιο βιβλιο, το ειχα αγορασει στα τελη του 1997. Πως σου φαινεται; Το διαβασες ολοκληρο;

Δημοσ.

Δοκιμασε τον παρακατω κωδικα :

 

 

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

#define BUFFER_SIZE 1024

#define RESTRICT_CLONING(class_name) \
private : \
class_name & operator=(const class_name &); \
class_name (const class_name &);

class Object
{
public:
virtual std::string ToString() const = 0;
};

class LiquidAccumulator : public Object
{
const double _capacity;
double _size;
protected:
LiquidAccumulator (double capacity) :  _capacity (capacity), _size(0)
{
}
public:

virtual double GetCapacityInLiters() const
{
	return _capacity;
}

virtual double GetCurrentFullness() const 
{
	return _capacity > 0 ? (_size / _capacity) * 100.0 : 0;
}

virtual bool IsFull() const
{
	return _size >= _capacity;
}

virtual double Remove(double quantity)
{
	if(quantity > _size)
		quantity = _size;

	_size -= quantity;
	
	return quantity;
}

virtual double Add(double quantity) // returns the remainding quantity if the Accumulator was full
{
	const double availableCapacity = _capacity - _size;
	const double howMuchToAdd = quantity > availableCapacity ? availableCapacity : quantity;
	_size += howMuchToAdd;
	return quantity - howMuchToAdd;
}

virtual std::string ToString() const
{
	char buffer[bUFFER_SIZE];
	sprintf(buffer, "Maximum Capacity: %4.2f, Current size: %4.2f Fullnes %0.2f%%", 
		_capacity, _size, GetCurrentFullness());
	return buffer; 
}
};


class LiquidStream : public Object
{

private:
const double _capacity; // liters / min

LiquidAccumulator* _pConnection;

protected:

LiquidStream(double capacity) : _capacity(capacity), _pConnection(NULL)
{
}

public:
virtual double Capacity() const 
{
	return _capacity;
}

virtual void Connect(LiquidAccumulator* pConnection)
{
	_pConnection = pConnection;
}

virtual void Disconnect()
{
	_pConnection = NULL;
}

virtual void Run(double dourationInMinutes)
{
	if(NULL != _pConnection)
		_pConnection->Add(_capacity * dourationInMinutes);
}

};


class Tap : public LiquidStream
{
RESTRICT_CLONING(Tap)


public:
Tap(double capacity) : LiquidStream(capacity)
{
}

virtual std::string ToString() const
{
	char buffer[bUFFER_SIZE];
	sprintf(buffer, "(Tap) Capacity: %4.2f liters/min", Capacity());
	return buffer; 
}

};


class Pot : public LiquidAccumulator 
{
RESTRICT_CLONING(Pot)

public:

Pot(int capacity): LiquidAccumulator(capacity)
{
	
}

virtual std::string ToString() const
{
	char buffer[bUFFER_SIZE];
	sprintf(buffer, "(Pot) %s", LiquidAccumulator::ToString().c_str());
	return buffer; 
}


};


class SwimingPool: public LiquidAccumulator
{
RESTRICT_CLONING(SwimingPool)

public:

SwimingPool(double capacity) : LiquidAccumulator(capacity)
{
}

virtual std::string ToString() const
{
	char buffer[bUFFER_SIZE];
	sprintf(buffer, "(SwimmingPool) %s", LiquidAccumulator::ToString().c_str());
	return buffer; 
}
};



void main()
{

SwimingPool pool(1000);

Tap tap(80);

cout << pool.ToString() << endl;	

tap.Connect(&pool);
tap.Run(10);

cout << pool.ToString() << endl;	

Pot pot(100);

pot.Add(pool.Remove(30));

cout << pool.ToString() << endl;	

cout << pot.ToString() << endl;		
}

Δημοσ.

Το βιβλιο, αρκετα διαφωτιστικο, στα σημεια που ηθελα ( Δεν ξερω αν θα βοηθουσε καποιον απο την αρχη, ομως εμενα που ειχα αρκετο καιρο να ακουμπισω c++ με καληψε!) Προς τον DeltaLover... Φιλε του εδωσες και καταλαβε!!! το εφτιαξες πολυ επαγγελματικα, το δικο μου ειναι αρκετα ερασιτεχνικο, αλλα το εχω στειλει ιδη για βαθμολογηση... Παντως πολυ καλη δουλεια!

  • 2 εβδομάδες αργότερα...
Δημοσ.

Αν επιτρέπεται, κάποιες παρατηρήσεις στον ενδιαφέροντα κώδικα του DeltaLover:

α) Γιατί πρέπει όλες οι συναρτήσεις να είναι virtual; π.χ. η GetCurrentFullness() ή η IsFull() δεν βλέπω γιατί θα έπρεπε να αλλάξουν υλοποίηση σε υποκλάσεις.

β) Αυτή η κλάση Object με τη συνάρτηση ToString δεν θυμίζει καθόλου C++. Ο πιο καλός τρόπος για να τυπώνεις αντικείμενα (γιατί για αυτό πρόκειται ουσιαστικά) θα ήταν με ορισμό του operator<<().

 

- Τα (α) και (β) μοιάζουν με μεταφορά της Java στην C++: στην Java όλες οι συναρτήσεις των κλάσεων είναι «virtual» και επιπλέον εκεί υπάρχει η υπερκλάση Object με τη συνάρτηση toString(). Αλλά δεν το βρίσκω καλό να μεταφέρονται τέτοια (απλά) ιδιώματα από τη μία γλώσσα στην άλλη όταν υπάρχουν μηχανισμοί για αντίστοιχες λειτουργίες.

 

γ) Δεν γίνεται καλή χρήση των δυνατοτήτων της C++, π.χ. όταν χρησιμοποιείται char[] και sprintf. Ακόμα και το απλό string θα μπορούσε να κάνει τη δουλειά, ή για πιο συγκεκριμένες απαιτήσεις, ένα stringstream.

Δημοσ.

Αν επιτρέπεται, κάποιες παρατηρήσεις στον ενδιαφέροντα κώδικα του DeltaLover:

α) Γιατί πρέπει όλες οι συναρτήσεις να είναι virtual; π.χ. η GetCurrentFullness() ή η IsFull() δεν βλέπω γιατί θα έπρεπε να αλλάξουν υλοποίηση σε υποκλάσεις.

β) Αυτή η κλάση Object με τη συνάρτηση ToString δεν θυμίζει καθόλου C++. Ο πιο καλός τρόπος για να τυπώνεις αντικείμενα (γιατί για αυτό πρόκειται ουσιαστικά) θα ήταν με ορισμό του operator<<().

 

- Τα (α) και (β) μοιάζουν με μεταφορά της Java στην C++: στην Java όλες οι συναρτήσεις των κλάσεων είναι «virtual» και επιπλέον εκεί υπάρχει η υπερκλάση Object με τη συνάρτηση toString(). Αλλά δεν το βρίσκω καλό να μεταφέρονται τέτοια (απλά) ιδιώματα από τη μία γλώσσα στην άλλη όταν υπάρχουν μηχανισμοί για αντίστοιχες λειτουργίες.

 

γ) Δεν γίνεται καλή χρήση των δυνατοτήτων της C++, π.χ. όταν χρησιμοποιείται char[] και sprintf. Ακόμα και το απλό string θα μπορούσε να κάνει τη δουλειά, ή για πιο συγκεκριμένες απαιτήσεις, ένα stringstream.

 

Αυτα που λες δεν ειναι αντιγραφη απο java, απλος ειναι object oriented

Δημοσ.

Δηλαδή χωρίς αυτά δεν θα ήταν object-oriented;

 

Αυτό που θέλω να πω το είπα ήδη: κάθε γλώσσα έχει τους δικούς της ιδιωματισμούς, το δικό της στυλ που κάνει κάποια πράγματα -- και δεν θεωρώ σωστό να μεταφέρεται το στυλ της μίας γλώσσας σε άλλη. Παρεμπιπτόντως, στη C++ συνηθίζεται να γράφεται στο .h η δήλωση της κλάσης και στο .cpp η υλοποίηση των συναρτήσεών της (ενώ στη Java όλα μαζί!)

 

Στο συγκεκριμένο, ιδιαίτερα η άσκοπη χρήση του virtual παραβαίνει μία από τις βασικές «αρχές» της C++ (και διαφορά σε σχέση με την Java): ο προγραμματιστής έχει τη δυνατότητα να ρυθμίζει τον κώδικά του με μεγάλη λεπτομέρεια, και είναι στο χέρι του να κάνει τις πιο αποδοτικές ρυθμίσεις -- κάτι που σίγουρα δεν γίνεται χαρακτηρίζοντας όλες τις συναρτήσεις virtual άσχετα με το αν έχει νόημα να γίνει υπέρβασή τους σε υποκλάσεις.

Δημοσ.

Δηλαδή χωρίς αυτά δεν θα ήταν object-oriented;

 

Αυτό που θέλω να πω το είπα ήδη: κάθε γλώσσα έχει τους δικούς της ιδιωματισμούς, το δικό της στυλ που κάνει κάποια πράγματα -- και δεν θεωρώ σωστό να μεταφέρεται το στυλ της μίας γλώσσας σε άλλη. Παρεμπιπτόντως, στη C++ συνηθίζεται να γράφεται στο .h η δήλωση της κλάσης και στο .cpp η υλοποίηση των συναρτήσεών της (ενώ στη Java όλα μαζί!)

 

Στο συγκεκριμένο, ιδιαίτερα η άσκοπη χρήση του virtual παραβαίνει μία από τις βασικές «αρχές» της C++ (και διαφορά σε σχέση με την Java): ο προγραμματιστής έχει τη δυνατότητα να ρυθμίζει τον κώδικά του με μεγάλη λεπτομέρεια, και είναι στο χέρι του να κάνει τις πιο αποδοτικές ρυθμίσεις -- κάτι που σίγουρα δεν γίνεται χαρακτηρίζοντας όλες τις συναρτήσεις virtual άσχετα με το αν έχει νόημα να γίνει υπέρβασή τους σε υποκλάσεις.

 

Ετσι οπος δουλευει ο c++ compiler (κατα τι γνωμη μου) οταν θελεις ενα OO project πρεπει ολες σου οι συναρτησεις να ειναι virtual. Οι λογοι ειναι πολλοι, ο πιο βασικος, κουμπωνεις την συναρτηση στο object και δεν ειναι αθαιρετο μεσα στο module, αυτο σου δινει τη δυνατοτητα του πολυμορφισμου, την εξαγωγη του object απο το module κλπ κλπ. (Αυτο μπορεις να το δεις και στα γνωστα API/freamworks πχ DX, ATL, COM κλπ κλπ, τα παντα ειναι virtual)

 

Τωρα για το που γραφεις μια class.. Εξαρταται. Οταν εχεις μια class που ειναι inline τοτε αυτη πρεπει ναι ειναι μεσα σε ενα αρχειο (το declaration και το definition) πχ αν εχεις μια class με template δεν μπορεις να τη βαλεις σε h και cpp, διοτι δεν μπορει να γινει compile (δεν μπορεις να φτιαξεις object file)

Δημοσ.

Ετσι οπος δουλευει ο c++ compiler (κατα τι γνωμη μου) οταν θελεις ενα OO project πρεπει ολες σου οι συναρτησεις να ειναι virtual. Οι λογοι ειναι πολλοι, ο πιο βασικος, κουμπωνεις την συναρτηση στο object και δεν ειναι αθαιρετο μεσα στο module, αυτο σου δινει τη δυνατοτητα του πολυμορφισμου, την εξαγωγη του object απο το module κλπ κλπ. (Αυτο μπορεις να το δεις και στα γνωστα API/freamworks πχ DX, ATL, COM κλπ κλπ, τα παντα ειναι virtual)

 

Το μόνο που κατάλαβα από αυτά και που ισχύει είναι ότι το virtual είναι απαραίτητο για να έχεις τη δυνατότητα πολυμορφισμού.

Τι εννοείς «κουμπωνεις την συναρτηση στο object και δεν ειναι αθαιρετο μεσα στο module»; Επίσης «την εξαγωγη του object απο το module»;

Το virtual έχει συγκεκριμένη χρησιμότητα -- και, στην C++, κάποιο κόστος σε επιδόσεις. Όταν δεν έχει νόημα, πολύ απλά δεν πρέπει να χρησιμοποιείται.

 

Τωρα για το που γραφεις μια class.. Εξαρταται. Οταν εχεις μια class που ειναι inline τοτε αυτη πρεπει ναι ειναι μεσα σε ενα αρχειο (το declaration και το definition) πχ αν εχεις μια class με template δεν μπορεις να τη βαλεις σε h και cpp, διοτι δεν μπορει να γινει compile (δεν μπορεις να φτιαξεις object file)

Μία κλάση δεν είναι ποτέ inline. Μία συνάρτηση μπορεί να είναι inline και τότε όντως γράφεται και η υλοποίησή της μέσα στο .h αρχείο. Όμως όλες οι υπόλοιπες συναρτήσεις συνηθίζεται να γράφονται χωριστά. Αν τις γράψουμε μέσα στο .h αρχείο δεν κερδίζουμε τίποτα, μόνο χάνουμε σε χρόνο μεταγλώττισης γιατί μεταγλωττίζονται κάθε φορά -- ενώ αν ήταν μέσα σε .cpp αρχείο θα μπορούσε να φτιαχτεί το αντίστοιχο .o αρχείο και να μην μεταγλωττίζονται αν δεν αλλάξουν.

 

Επιπλέον, η απόφαση για το αν τελικά θα γίνει inline μία συνάρτηση ανήκει αποκλειστικά στον compiler: ακόμα και αν εμείς γράψουμε μία συνάρτηση inline (= την υλοποίησή της μέσα στο .h αρχείο) αν ο compiler δεν το θεωρεί «σωστό» δεν θα την κάνει inline με τίποτα.

 

Τέλος, ο λόγος που οι template κλάσεις γράφονται ολόκληρες μέσα στο .h αρχείο είναι τεχνικός (ο compiler δεν γνωρίζει τον ακριβή τύπο και άρα δεν μπορεί να μεταγλωττίσει τον κώδικα από «νωρίς», πριν δημιουργηθεί συγκεκριμένη εκδοχή του template για συγκεκριμένο τύπο). Είχε γίνει πρόταση για χωριστή υλοποίηση των templates σε .h και .cpp αλλά δεν προχώρησε και πλέον καταργήθηκε.

Δημοσ.

Αν επιτρέπεται, κάποιες παρατηρήσεις στον ενδιαφέροντα κώδικα του DeltaLover:

α) Γιατί πρέπει όλες οι συναρτήσεις να είναι virtual; π.χ. η GetCurrentFullness() ή η IsFull() δεν βλέπω γιατί θα έπρεπε να αλλάξουν υλοποίηση σε υποκλάσεις.

β) Αυτή η κλάση Object με τη συνάρτηση ToString δεν θυμίζει καθόλου C++. Ο πιο καλός τρόπος για να τυπώνεις αντικείμενα (γιατί για αυτό πρόκειται ουσιαστικά) θα ήταν με ορισμό του operator<<().

 

- Τα (α) και (β) μοιάζουν με μεταφορά της Java στην C++: στην Java όλες οι συναρτήσεις των κλάσεων είναι «virtual» και επιπλέον εκεί υπάρχει η υπερκλάση Object με τη συνάρτηση toString(). Αλλά δεν το βρίσκω καλό να μεταφέρονται τέτοια (απλά) ιδιώματα από τη μία γλώσσα στην άλλη όταν υπάρχουν μηχανισμοί για αντίστοιχες λειτουργίες.

 

γ) Δεν γίνεται καλή χρήση των δυνατοτήτων της C++, π.χ. όταν χρησιμοποιείται char[] και sprintf. Ακόμα και το απλό string θα μπορούσε να κάνει τη δουλειά, ή για πιο συγκεκριμένες απαιτήσεις, ένα stringstream.

 

 

α) Νομιζω η class αυτη απαντα στην ερωτηση σου:

>class VariableCapacityLiquidAccumulator: public LiquidAccumulator
{
double _capacity;
       double _size;
public:
	VariableCapacityLiquidAccumulator (double initialCapacity) :  _capacity (initialCapacity), _size(0), LiquidAccumulator(initialCapacity)
       {
       }

       virtual double GetCapacityInLiters() const
       {
               return _capacity;
       }

       virtual double GetCurrentFullness() const 
       {
               return _capacity > 0 ? (_size / _capacity) * 100.0 : 0;
       }

       virtual bool IsFull() const
       {
               return _size >= _capacity;
       }

       virtual double Remove(double quantity)
       {
               if(quantity > _size)
                       quantity = _size;

               _size -= quantity;
               
               return quantity;
       }

	virtual void AdjustCapacity(double addedCapacity)
	{
		_capacity += addedCapacity;
		if(_capacity <0)
			_capacity = 0;
	}

       virtual double Add(double quantity) // returns the remainding quantity if the Accumulator was full
       {
               const double availableCapacity = _capacity - _size;
               const double howMuchToAdd = quantity > availableCapacity ? availableCapacity : quantity;
               _size += howMuchToAdd;
               return quantity - howMuchToAdd;
       }

       virtual std::string ToString() const
       {
               char buffer[bUFFER_SIZE];
               sprintf(buffer, "Maximum Capacity: %4.2f, Current size: %4.2f Fullnes %0.2f%%", 
                       _capacity, _size, GetCurrentFullness());
               return buffer; 
       }
};

 

Εφοσον εχουμε προβλεψει το public contact να μας επιτρεπει polymorphism μπορουμε ευκολα να κανουμε extend την hierarchy παρακαμπτωντας το οτι στην βαση της το capacity οριζεται const. Αν η IsFull δεν ηταν virtual η VariableCapacityLiquidAccumulator δεν θα μπορουσε να χρησιμοποιειθει πολυμορφικα αφου ενας pointer στην LiquidAccumulator θα μας εδινε τελειως λαθος αποτελεσματα.....

 

Γενικα μιλωντας οταν σχεδιαζουμε μια καθετα πολυμορφικη ιεραρχια προσπαθουμε ολα τα public και protected functions να τα κανουμε virtual. Ειναι αληθες βεβαια οτι αυτες δεν μπορουν να γινουν inline αλλα απο πλευρας interface design δεν εχουμε αλλη επιλογη... Ενας τροπος να κανουμε expose τα interfaces με τετοιο τροπο ενω το implementation να χρησιμοποιει early binding (σε αντιθεση με lazy evaluation) ειναι να ακολουθησουμε το περιφημο pimpl pattern η αλλιως document - envelope...

 

β) Η συζητηση πανω στην χρηση και ενδεχομενα την καταχρηση του operator overload ειναι πολυ μεγαλη... Να σου δωσω ενα παραδειγμα: Για να κανεις implement εναν redirection operator (πχ >>) πρεπει υποχρεωτικα να χρησιμοποιησεις friendship κατι που φυσικα αντικειται στον OOP, αφου αυτη η friend θα ειναι ελευθερη function και φυσικα δεν ειναι polymorphic... Ενα κοινα απαντωμενο idiom οταν χρειαζομαστε κατι τετοιο ειναι αυτο:

 

>struct X
{
std::ostream& print(std::ostream& out) const
{ 
	return out << "I am a X";
} 
};

struct Y : X
{
};

std::ostream& operator<<(std::ostream& out,const X& x)
{
   return x.print(out);
}


void main()
{
Y y;
std::cout << y << std::endl;
}

 

 

 

Νομιζω οτι το προβλημα ειναι προφανες...

 

Βεβαια το συγκεκριμενο προβλημα λυνεται ευκολα:

 

>struct X
{
virtual std::ostream& print(std::ostream& out) const
{ 
	return out << "I am a X";
} 
};

struct Y : X
{
virtual std::ostream& print(std::ostream& out) const
{ 
	return out << "I am a Y";
}
};

 

Ειναι ομως ενδεκτικο των παρενεργειων που μπορουμε να εχουμε με την οχι προσεκτικη εφαρμογη του operator overloading....

 

Οταν γραφουμε ενα framework ειναι καθιερωμενο να κανουμε derive ολες τικς classes απο μια κοινη βαση η οποια κανει expose την βασικη functionality που περιμενουμε απο καθε κλαση οπως περιπου ειναι στην C# και στην Java και σχεδον παντα υπαχει σε αυτη μια function παρομοια με το ToString.

 

γ) Εδω εχεις δικιο.. Το stringstream ειναι ο C++ τροπος για να φορμαρυουμε ενα string... Στην πραξη παντως το sprintf εξακολουθει να χρησιμοποιειται ευρεως, ισως γιατι ειναι πιο βολικο ισως γιατι ειναι πιο γρηγορο....

Δημοσ.

Τα επιχειρήματά σου για το virtual είναι αρκετά έγκυρα. Αυτό που υποστηρίζω όμως είναι ότι πρέπει να λαμβάνουμε υπόψιν το στόχο που έχουμε σε κάθε υλοποίηση. Δηλαδή αν σχεδιάζουμε μία βαθιά ιεραρχεία κλάσεων (ή έστω μία που περιμένουμε ότι θα επεκταθεί και προς κατευθύνσεις που ίσως δεν έχουμε προβλέψει) τότε πρέπει μάλλον να πάμε σε virtual συναρτήσεις (ή ίσως και καλύτερα σε αφηρημένες συναρτήσεις -- interface που θα λέγαμε και στην Java :-) ). Αν όμως τα πράγματα είναι πιο περιορισμένα και ελεγχόμενα, τότε δεν υπάρχει λόγος να γίνονται όλα virtual. Αυτός είναι και ο λόγος που με ξένισε κάπως η υλοποίησή σου, καθώς το αρχικό ερώτημα αναφερόταν σε μία σαφώς περιορισμένη ιεραρχία κλάσεων, στην οποία μάλιστα θα είχε αξία να μπορεί κάποιος να ξεχωρίσει τι πρέπει να είναι virtual και τι όχι.

 

Όσο για τους operator, ασφαλώς και η συζήτηση είναι πολύ μεγάλη. Γιατί γράφεις όμως ότι «Για να κανεις implement εναν redirection operator (πχ >>) πρέπει υποχρεωτικά να χρησιμοποιήσεις friendship» -- άλλωστε ούτε εσύ δεν χρησιμοποιείς στο παράδειγμά σου! Επιπλέον για το παράδειγμά σου, δεν βλέπω το «προφανές» πρόβλημα: αυτά που γράφουμε, αυτά γίνονται. Κάτι θέλεις να δείξεις (μάλλον αυτό που λες, ότι από μόνος του o operator>>() δεν είναι πολυμορφικός) αλλά δεν δείχνει αυτό ο κώδικάς σου (και κάπου είχα ένα σχετικό παράδειγμα, αλλά δεν μου έρχεται τώρα :-) )

Δημοσ.

Τα επιχειρήματά σου για το virtual είναι αρκετά έγκυρα. Αυτό που υποστηρίζω όμως είναι ότι πρέπει να λαμβάνουμε υπόψιν το στόχο που έχουμε σε κάθε υλοποίηση. Δηλαδή αν σχεδιάζουμε μία βαθιά ιεραρχεία κλάσεων (ή έστω μία που περιμένουμε ότι θα επεκταθεί και προς κατευθύνσεις που ίσως δεν έχουμε προβλέψει) τότε πρέπει μάλλον να πάμε σε virtual συναρτήσεις (ή ίσως και καλύτερα σε αφηρημένες συναρτήσεις -- interface που θα λέγαμε και στην Java :-) ). Αν όμως τα πράγματα είναι πιο περιορισμένα και ελεγχόμενα, τότε δεν υπάρχει λόγος να γίνονται όλα virtual. Αυτός είναι και ο λόγος που με ξένισε κάπως η υλοποίησή σου, καθώς το αρχικό ερώτημα αναφερόταν σε μία σαφώς περιορισμένη ιεραρχία κλάσεων, στην οποία μάλιστα θα είχε αξία να μπορεί κάποιος να ξεχωρίσει τι πρέπει να είναι virtual και τι όχι.

 

Όσο για τους operator, ασφαλώς και η συζήτηση είναι πολύ μεγάλη. Γιατί γράφεις όμως ότι «Για να κανεις implement εναν redirection operator (πχ >>) πρέπει υποχρεωτικά να χρησιμοποιήσεις friendship» -- άλλωστε ούτε εσύ δεν χρησιμοποιείς στο παράδειγμά σου! Επιπλέον για το παράδειγμά σου, δεν βλέπω το «προφανές» πρόβλημα: αυτά που γράφουμε, αυτά γίνονται. Κάτι θέλεις να δείξεις (μάλλον αυτό που λες, ότι από μόνος του o operator>>() δεν είναι πολυμορφικός) αλλά δεν δείχνει αυτό ο κώδικάς σου (και κάπου είχα ένα σχετικό παράδειγμα, αλλά δεν μου έρχεται τώρα :-) )

 

Αριστη η παρατηρηση σου...

 

Νομιζω ομως οτι η παρακατω διευκρινηση θα σε βοηθησει να καταλαβεις το νοημα της παραπανω προτασης....

 

Οταν λεω υποχρεωτικα πρεπει να χρησιμοποιησεις friendship δεν αναφερομαι σε compilation level compliance.... Οπως πολυ σωστα επισημαινεις το snippet που παραθετω ενω δεν χρησιμοποιει friendship αποτελει απολυτα valid C++ κωδικα... Ο λογος που 'πρεπει' να χρησιμοποιησεις friendship οταν κανεις overload τα redirection operators (και κατ' επεκταση οποιον αλλον operator απαιτει non-member function δεν εχει να κανει τοσο με το οτι μπορει να απαιτειται προσβαση σε encapsulated data οσο το οτι αυτο ακριβως το declaration σε υποχρεωνει να δωσεις specific implementation για τα overloads αλλιως θα εχεις compile level error... Αποτελει δηλαδη standard pattern σε τετοιες περιπτωσεις... Αν θελεις, μπορεις να πεις οτι ειναι ενα χρησιμο programming habit...

 

Για παραδειγμα αυτο το προγραμμα:

 

>#include <iostream>
#include <string>
struct X
{
std::ostream& print(std::ostream& out) const
{ 
	return out << "I am a X";
}

friend std::ostream& operator<<(std::ostream& out,const X& x);

};
struct Y : X
{
friend std::ostream& operator<<(std::ostream& out,const Y& x);
};

std::ostream& operator<<(std::ostream& out,const X& x)
{
   return x.print(out);
}


void main()
{
Y y;
std::cout << y << std::endl;
int i;
std::cin >> i;
}

 

 

 

Στο οποιο ο developer εβαλε το friend std::ostream& operator<<(std::ostream& out,const Y& x); δεν θα γινει compile, το bug θα πιαστει ευκολα και αμεσως...

 

Παρατηρησε τελος, οτι to declaration των friends θα επιβαρυνει το global namespace εφοσον αυτα θα πρεπει να γινουν declared (συνηθως στο ιδιο .h που κανει declare την κλαση... Αυτο το namespace pollution σιγουρα πρεπει να αποφευγεται καθως επιβαρυνει το coupling με τους clients του κωδικα μας κανωντας μελλοντικες αλλαγες στον κωδικα μας πιο δυσκολες αλλα και μειωνοντας το flexibility του χρηστη του.

Δημοσ.

Δυστυχώς πάλι κάτι δεν καταλαβαίνω: δηλαδή αν οι δύο operator<<() δεν ήταν friend αλλά δηλωμένοι απλά έξω από τις κλάσεις (καθώς δεν απαιτούν πρόσβαση σε μη-public τμήματα) ποια διαφορά θα υπήρχε; Εγώ δεν βρήκα καμία δοκιμάζοντας το πρόγραμμά σου...

 

Όσο για το namespace pollution: αν είναι πρόβλημα βάζεις την κλάση σου (μαζί με όποιες ελεύθερες συναρτήσεις τη συνοδεύουν) σε ένα δικό σου namespace :-) Αφού οι διάφοροι operator που τυχόν θα οριστούν ως ελεύθερες συναρτήσεις συνοδεύουν και επαυξάνουν την κλάση, δεν βλέπω γιατί αποτελούν «pollution». Έτσι κι αλλιώς, οι operator δεν είναι τίποτα άλλο από «syntactic sugar».

 

[Και εκείνο το main() πρέπει να γίνει int main()]

Δημοσ.

Η διαφορα ειναι οτι το δευτερο προγραμμα δεν θα γινει link... Θα σου δωσει το παρακατω η ενα παρομοιο error:

>main.obj : error LNK2019: unresolved external symbol "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,struct Y const &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@ABUY@@@Z) referenced in function _main

 

 

Οσον αφορα το return value εχεις δικιο οτι χρησιμοποιωντας int main() ειναι καλυτερο (και standard) απο void main() αν και η δευτερη εκδοχη ηταν σε ευρεια χρηση στο παρελθον (ειδικα πριν το ISO 98)....

 

Γενικα ισως μπορεις να χαρακτηρησεις το overloading σαν syntactic sugar αν και καποιος μπορει να διαφωνισει με αυτο... Συνηθως χρησιμοποιουμε τον ορο αυτο σε περιπτωσεις οπου η γλωσσα μας παρεχει μια συντομογραφια ενος verbose declaration η οποια ομως καταληγει σε πολυ αναλογο compiled code. Στο παραδειγμα μας ομως εχουμε το declaration μιας επιπλεον function η οποια οπως ειπα παραπανω κανει pollute το namespace (δεν εχει σημασια αν ειναι το global η ενα custom namespace ειναι παντα ενα επιπλεον entry point)

Αρχειοθετημένο

Αυτό το θέμα έχει αρχειοθετηθεί και είναι κλειστό για περαιτέρω απαντήσεις.

  • Δημιουργία νέου...