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

[C++] Template specialization


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

  • Moderators
Δημοσ.

Καλή χρονιά και χρόνια πολλά σε όλους τους αγαπητούς χρήστες του Insomnia!

 

Στο project που ασχολούμαι έχω έναν resource manager, του οποίου η δουλειά είναι να φορτώνει assets και να δίνει references σε όποιον τα ζητάει. Για να απλοποιήσω τη χρήση του, έχω φτιάξει μία συνάρτηση RequestAsset:

template <typename AssetType>
auto RequestAsset(std::string ID) -> decltype(AssetType const &)
{
    switch (decltype(AssetType))
    {
        case GLuint:
            if (Textures.count(ID) == 0)
            {
                Textures.emplace(ID, LoadTexture(ID));
                Textures[ID].ID = ID;
            }

            Textures[ID].RefCounter++;
            return Textures[ID].Asset;

            break;

        case std::string:
            if (Strings.count(ID) == 0)
            {
                Strings.emplace(ID, LoadText(ID));
                Strings[ID].ID = ID;
            }

            Strings[ID].RefCounter++;
            return Strings[ID].Asset;

            break;
    }
}

Σε κάποιο άλλο σημείο έχω αυτό:

auto MapDataRaw = MResourceManager::Instance().RequestAsset<std::string>(ID);

το οποίο δεν αρέσει στον compiler. Δοκίμασα διάφορα πράγματα (throwing shit against the wall...) για να το κάνω να δουλέψει αλλά δε θέλει. Αν βγάλω το trailing type δε δουλεύει το switch, αν αφήσω το trailing type μου πετάει error ότι δε μπορεί να κάνει specialize το function.

 

Δε μπορώ να καταλάβω πού κάνω λάθος εδώ. Η εναλλακτική που έχω σκεφτεί είναι να κάνω specialize το template για κάθε τύπο asset αλλά αυτή η προοπτική δε με τρελαίνει.

 

Αυτό που με τρελαίνει είναι ότι όσο έγραφα αυτό το post έκανε compile κανονικά, άλλαξα κάτι κάπου αλλού και τώρα μου πετάει πάλι errors. Έκανα undo τις αλλαγές που έκανα και πάλι δεν κάνει compile (αυτό που έκανε μια χαρά πριν τις αλλαγές που έκανα undo!). Πιο κάτω στον manager έχω αυτό εδώ:

template <typename AssetType>
void ReleaseAsset(std::string ID)
{
    switch (decltype(AssetType))
    {
        case GLuint:
            Textures[ID].RefCounter--;
            if (Textures[ID].RefCounter == 0)
            {
                GL_CALL(glDeleteTextures(1, &ID));
                Textures.erase(ID);
            }
            break;
    }
}

Κάνω compile, μου πετάει error (expecting decltype(auto)). Κάνω comment το switch, κάνει compile. Βγάζω τα comments απ' το switch, κάνει compile ακριβώς το ίδιο πράγμα που χτύπαγε 10 δευτερόλεπτα πριν!

 

Μπορεί κάποιος να με βοηθήσει λίγο και να μου πει ποιο διάλο είναι το πρόβλημά του; Και αν αυτό που θέλω δε γίνεται έτσι όπως το έχω, αν τα template specializations που ανέφερα παραπάνω αποτελούν την καλύτερη επιλογή που έχω.

 

Ευχαριστώ!

Δημοσ.

Με επιφύλαξη επειδή δεν έχω ασχοληθεί πολύ με αυτά τα καινούρια τα κοκοψόψαρα στη c++ (θα ήταν καλή ερώτηση για SO, κάντην κι εκεί)...

 

Πρώτα απ' όλα δε μπορείς να κάνεις switch(decltype()) με καμία παναγία γιατί το αποτέλεσμα της decltype δεν είναι expression (μπακάλικα, το σωστό είναι "condition" και είναι λίγο πιο μεγάλο σύνολο). Οπότε αυτή η ιδέα δεν πρόκειται να κάνει compile ποτέ.

 

Τώρα, τι ακριβώς προσπαθείς να κάνεις εδώ; Θα έλεγε κανείς, compile-time switch στο statically known AssetType. Αλλά αφού θες compile-time switch τότε γιατί μπλέκεις εξαρχής με switch statement που είναι runtime-evaluated? Οπότε, decltype or not και μόνο το switch που κάνεις φαίνεται λίγο random επιλογή.

 

Επίσης, το auto RequestAsset(std::string ID) -> decltype(AssetType const &) δεν έχει νόημα -- υποθέτω ήταν μέρος του throwing shit against the wall? Το ίδιο θα ήταν να γράψεις AssetType const & RequestAsset(std::string ID) εφόσον δεν εμπλέκεται κανένα expression για το return type του οποίου δεν είσαι σίγουρος σ' αυτό που κάνεις.

 

Η φαινομενικά αλλοπρόσαλλη συμπεριφορά των compile time errors είναι κλασική C++ όταν έχεις πολλά λάθη στον κώδικα, μη δίνεις σημασία. Απλά πρέπει να αντιμετωπίσεις κάθε προσπάθεια ξεχωριστά χωρίς να μπαίνεις στο τριπάκι "πιο πριν αυτό ήταν ΟΚ αλλά δε δούλευε το άλλο".

 

TL;DR template specialization για κάθε asset type, και γιατί δε θέλεις να το κάνεις έτσι; Δεν έχει και καμία διαφορά στην ουσία του με αυτό που προσπαθείς να κάνεις εδώ, εκτός του ότι είναι compile time εγγυημένο ενώ αυτό εδώ δε θα μπορούσε ποτέ να είναι λόγω του switch.

Δημοσ.

Οπως σου ειπα και πριν, καντο oo. Με τα template θα εχεις πολλα θεματα, επειδη δεν γινονται compile αν δεν εχουν call. Δηλαδη μπορεις να εχεις μια μακαροναδα η οποια δεν κανει compile, αλλα επειδη δεν εχεις call πανω της, ο compiler να σου πει οτι ειναι OK. Πχ το switch που ειναι λαθος θα βαραει error, αλλα αν βγαλεις το manager instance requestaset τοτε δεν θα εχεις errors.

  • Moderators
Δημοσ.

Οπως σου ειπα και πριν, καντο oo.

 

Τι εννοείς ΟΟ; Δηλαδή πέρα απ' το να έχω μια συνάρτηση RequestTexture, μία RequestString κοκ τι άλλο θα μπορούσα να κάνω;

 

 

και γιατί δε θέλεις να το κάνεις έτσι; Δεν έχει και καμία διαφορά στην ουσία του με αυτό που προσπαθείς να κάνεις εδώ

 

Γιατί υπέθεσα ότι θα μπορούσε να γίνει χωρίς να γεμίσω τον κώδικα specializations αλλά απ' ό,τι φαίνεται δε γίνεται.

 

Θα το κάνω με specializations λοιπόν, εκτός αν το παπι μου πει τι εννοεί με το ΟΟ.

 

Σας ευχαριστώ.

Δημοσ.

Με τα template θα εχεις πολλα θεματα, επειδη δεν γινονται compile αν δεν εχουν call. Δηλαδη μπορεις να εχεις μια μακαροναδα η οποια δεν κανει compile, αλλα επειδη δεν εχεις call πανω της, ο compiler να σου πει οτι ειναι OK. Πχ το switch που ειναι λαθος θα βαραει error, αλλα αν βγαλεις το manager instance requestaset τοτε δεν θα εχεις errors.

 

Καλά δεν είναι βέβαια το ζητούμενο να έχεις κώδικα που δε στέκει, αλλά γιατί τόσο δε σου αρέσει; Αυτό που λες "δεν κάνει compile αλλά επειδή δεν έχεις call" είναι στην πραγματικότητα "κάνει compile ακριβώς σύμφωνα με τους κανόνες".

 

Υπάρχουν περιπτώσεις όπου ένα template είναι επίτηδες γραμμένο έτσι που να μην κάνει compile αν το κάνεις instantiate, και αυτός είναι ο λόγος ύπαρξής του: σε template metaprogramming, αν καταλήξεις να πας να το κάνεις compile τρώς πόρτα επειδή έχεις πάει να κάνεις κάτι που είναι λογικά λάθος, όπως αυτό καθορίζεται από τα templates που συζητάμε. Και επίσης υπάρχει ολόκληρο language feature (SFINAE) που είναι χρήσιμο αποκλειστικά και μόνο όταν έχεις templates που για κάποιο σύνολο arguments δε μπορούν να γίνουν instantiate "λόγω compile error".

 

Τα templates γενικά είναι άλλη φάση και θέλει να τα βλέπεις με άλλο μάτι.

 

Γιατί υπέθεσα ότι θα μπορούσε να γίνει χωρίς να γεμίσω τον κώδικα specializations αλλά απ' ό,τι φαίνεται δε γίνεται.

 

Άμα γεμίσεις τον κώδικα specializations (που δε ξέρω καν γιατί το λες έτσι) η εναλλακτική λύση πώς θα κατέληγε; Ένα τεράστιο switch; Δε θα ήταν απλά χειρότερο αυτό;

Δημοσ.

Δεν καταλαβαίνω. Τι σχέση έχουν τα virtual functions με αυτό;

Υποθέτω πως αυτό εννοεί ο Πάπι με το ΟΟ. Δηλαδή πας σε λύση αντικειμένων, όπου έχουν ένα κοινό Interface, και επιπλέον επειδή γίνεται late binding. Δες το παράδειγμα!

Δημοσ.

Καλά δεν είναι βέβαια το ζητούμενο να έχεις κώδικα που δε στέκει, αλλά γιατί τόσο δε σου αρέσει; Αυτό που λες "δεν κάνει compile αλλά επειδή δεν έχεις call" είναι στην πραγματικότητα "κάνει compile ακριβώς σύμφωνα με τους κανόνες".

 

Υπάρχουν περιπτώσεις όπου ένα template είναι επίτηδες γραμμένο έτσι που να μην κάνει compile αν το κάνεις instantiate, και αυτός είναι ο λόγος ύπαρξής του: σε template metaprogramming, αν καταλήξεις να πας να το κάνεις compile τρώς πόρτα επειδή έχεις πάει να κάνεις κάτι που είναι λογικά λάθος, όπως αυτό καθορίζεται από τα templates που συζητάμε. Και επίσης υπάρχει ολόκληρο language feature (SFINAE) που είναι χρήσιμο αποκλειστικά και μόνο όταν έχεις templates που για κάποιο σύνολο arguments δε μπορούν να γίνουν instantiate "λόγω compile error".

 

Τα templates γενικά είναι άλλη φάση και θέλει να τα βλέπεις με άλλο μάτι.

 

 

Άμα γεμίσεις τον κώδικα specializations (που δε ξέρω καν γιατί το λες έτσι) η εναλλακτική λύση πώς θα κατέληγε; Ένα τεράστιο switch; Δε θα ήταν απλά χειρότερο αυτό;

 

Λαθος εκφραση το call. Μαλλον..

Οταν λεω call εννοω να εχει το template καποιο reference σε καποια TU. Δεν μιαλω για SFINAE 

Πχ

template <class T>
T foo(T& t)
{
	static_assert(0, "check me");
	return t;
}

int main()
{
    return 0;
}

Αυτο κανει compile, ασχετα αν στη foo εχεις κατι που δεν κανει compile. Φυσικα αν καλεσεις τη foo στη main, τοτε θα σου πεταξει error. Απο αυτη την αποψη ειναι παλουκι. 

Τι εννοείς ΟΟ; Δηλαδή πέρα απ' το να έχω μια συνάρτηση RequestTexture, μία RequestString κοκ τι άλλο θα μπορούσα να κάνω;

 

 

 

Γιατί υπέθεσα ότι θα μπορούσε να γίνει χωρίς να γεμίσω τον κώδικα specializations αλλά απ' ό,τι φαίνεται δε γίνεται.

 

Θα το κάνω με specializations λοιπόν, εκτός αν το παπι μου πει τι εννοεί με το ΟΟ.

 

Σας ευχαριστώ.

 

ΟΟ...

 

Εχεις μια κλαση ResourceManager, και εκει εχεις μια συναρτηση RequestAsset. Αυτη αντι να επιστρεφει string int whateva, μπορει πολυ απλα να επιστρεφει ενα object Asset. Εφοσον το asset ειναι object, μπορεις να του βαλεις και life cycle και η κλαση ResurceManager απλα να γινει ενα factory.

  • Moderators
Δημοσ.

Υποθέτω πως αυτό εννοεί ο Πάπι με το ΟΟ. Δηλαδή πας σε λύση αντικειμένων, όπου έχουν ένα κοινό Interface, και επιπλέον επειδή γίνεται late binding. Δες το παράδειγμα!

 

Δηλαδή βάσει αυτού που έγραψες εννοείς να έχω μια base Resource η οποία να γίνεται inherit από άλλα resources;

 

 

Εχεις μια κλαση ResourceManager, και εκει εχεις μια συναρτηση RequestAsset. Αυτη αντι να επιστρεφει string int whateva, μπορει πολυ απλα να επιστρεφει ενα object Asset. Εφοσον το asset ειναι object, μπορεις να του βαλεις και life cycle και η κλαση ResurceManager απλα να γινει ενα factory.

 

Απ' ό,τι καταλαβαίνω (και επειδή το factory το έχεις ξαναπροτείνει νομίζω) το factory σου φτιάχνει αντικείμενα τα οποία είναι υπεύθυνα για το lifetime τους και γενικά για ό,τι κάνουν, σωστά; Αυτό που θέλω εγώ είναι ο manager να έχει τον έλεγχο για το lifetime των assets. Αλλά και factory να έκανα, δε θα έπρεπε να το κάνω inherit για κάθε διαφορετικό τύπο asset; Αυτό το Asset που λες ότι θα επέστρεφα πώς ακριβώς θα το χρησιμοποιούσα για διαφορετικούς τύπους assets; Δηλαδή θα επέστρεφα το ίδιο αντικείμενο για texture και το ίδιο για μουσική; Και πάλι δε θα ήθελε templates αυτό; (ουσιαστικά αυτό έχω κάνει τώρα, τα assets αποθηκεύονται σε map Asset<T>).

 

Εκτός αν εννοείς αυτό που είπε ο Μ2000, να έχω δηλαδή ένα base resource το οποίο θα κάνω inherit για κάθε τύπο asset.

 

 

Άμα γεμίσεις τον κώδικα specializations (που δε ξέρω καν γιατί το λες έτσι) η εναλλακτική λύση πώς θα κατέληγε; Ένα τεράστιο switch; Δε θα ήταν απλά χειρότερο αυτό;

 

Αν μετράω σωστά έχω 4-5 το πολύ διαφορετικά assets, οπότε θα ήταν σχετικά μικρό.

Δημοσ.

Στη λογική των αντικειμένων πρέπει να βάζουμε τις μεθόδους μαζί. Αν χρησιμοποιείς το αντικείμενο ως παράμετρο σε συνάρτηση ώστε ο χειρισμός να γίνει εκεί, ως να ήταν το αντικείμενο απλά δεδομένα, τότε έχεις βγει από τον OO λογισμό.

Αφού λοιπόν πρέπει να έχεις μια βάση για τα αντικείμενα, για να έχει κοινές μεθόδους, πρέπει να έχεις επίσης και ένα τρόπο κάποιες μεθόδους να παίζουν ανάλογα με το διαφορετικό αντικείμενο. Αυτό ακριβώς σου προσφέρει αυτό που έδωσα παραπάνω! 

Δες εδώ...το Cat είναι μεν Animal αλλά όταν καλέσεις το eat Θα παίξει το δικό του και όχι του Animal.. Ναι λοιπόν πως γλιτώνεις το switch. Ενώ έχεις ένα πίνακα με Animal όταν καλείς σε κάθε αντικείμενο την eat..τότε γίνεται Late Binding..δηλαδή στην εκτέλεση καλείται η  eat η ανάλογη. (φαντάσου ότι έχεις το Cat1, Cat2 και λοιπά)

 

class Animal
{
public:
virtual void eat() { std::cout << "I'm eating generic food."; }
}
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."; }
}

Δημοσ.

Δηλαδή βάσει αυτού που έγραψες εννοείς να έχω μια base Resource η οποία να γίνεται inherit από άλλα resources;

 

 

 

Απ' ό,τι καταλαβαίνω (και επειδή το factory το έχεις ξαναπροτείνει νομίζω) το factory σου φτιάχνει αντικείμενα τα οποία είναι υπεύθυνα για το lifetime τους και γενικά για ό,τι κάνουν, σωστά; Αυτό που θέλω εγώ είναι ο manager να έχει τον έλεγχο για το lifetime των assets. Αλλά και factory να έκανα, δε θα έπρεπε να το κάνω inherit για κάθε διαφορετικό τύπο asset; Αυτό το Asset που λες ότι θα επέστρεφα πώς ακριβώς θα το χρησιμοποιούσα για διαφορετικούς τύπους assets; Δηλαδή θα επέστρεφα το ίδιο αντικείμενο για texture και το ίδιο για μουσική; Και πάλι δε θα ήθελε templates αυτό; (ουσιαστικά αυτό έχω κάνει τώρα, τα assets αποθηκεύονται σε map Asset<T>).

 

Εκτός αν εννοείς αυτό που είπε ο Μ2000, να έχω δηλαδή ένα base resource το οποίο θα κάνω inherit για κάθε τύπο asset.

 

 

 

Αν μετράω σωστά έχω 4-5 το πολύ διαφορετικά assets, οπότε θα ήταν σχετικά μικρό.

 

check this

 

 

// test.cpp : Defines the entry point for the console application.
//

#include <iostream>


class Asset
{
	int _refCount;
public:
	Asset() : _refCount(1) {}
	virtual ~Asset() {}
	virtual void AddRef()
	{
		_refCount++;
	}
	virtual void Release()
	{
		_refCount--;
		if (_refCount <= 0)
			delete this;
	}
	//syntactic sugar
	template <class T>
	T* As()
	{
		return dynamic_cast<T*>(this);
	}
};

class Memory : public Asset
{
public:
	Memory()
	{
		std::cout << "Memory created" << std::endl;
	}
	~Memory()
	{
		std::cout << "Memory destroyed" << std::endl;
	}
};

class AssetFactory
{
public:
	static Asset* CreateAsset(int type = 1)
	{
		switch (type)
		{
		case 1:
			return new Memory();
		default:
			return nullptr;
		}
	}
};
/*syntactic sugar*/
template <class T>
class Scope
{
	T *obj;
public:
	Scope(T *t) : obj(t){}
	~Scope()
	{
		obj->Release();
	}
	T* operator ->()
	{
		return obj;
	}
};

int main()
{
	{
		Scope<Memory> memory = AssetFactory::CreateAsset(1)->As<Memory>();
	}

    return 0;
}
 

 

 

  • Like 1
Δημοσ.

Αν μετράω σωστά έχω 4-5 το πολύ διαφορετικά assets, οπότε θα ήταν σχετικά μικρό.

 

Οπότε δε θα "γέμιζε και ο τόπος" με 4-5 specializations, καταλαβαινόμαστε.

 

----

 

Αυτή η φάση με τα typed assets είναι μια χαρά και πολλές φορές η προφανής επιλογή.

 

Αυτά που λέει το παπί είναι πρακτικά orthogonal με τα typed assets γιατί

  1. Έβαλε και ο ίδιος αναγνωρίζοντας τη χρησιμότητα μηχανισμό για typing, αν και χειρότερο μιας και είναι runtime.
  2. Αν θέλεις να έχεις πολυμορφικά assets με το μηχανισμό των templates δε σε εμποδίζει κανείς, απλά τα βάζεις να κάνουν inherit μια base class. Δεν παίζει ρόλο το signature της function που τα επιστρέφει. Μπορείς και να εξασφαλίσεις at compile time ότι το όποιο type argument κάνει inherit όπως θα έπρεπε απλά χρησιμοποιώντας μια enable_if.
  3. To lifetime management προφανώς είναι orthogonal με οτιδήποτε άλλο, το κάνεις όπως θες. Π.χ. δίνε shared_ptr.

Στην ουσία μου φαίνεται ότι είναι μια πιο εξειδικευμένη εκδοχή (χωρίς φανερό λόγο όμως για την εξειδίκευση) η οποία είναι και κάπου χειρότερη στα σημεία (στο παράδειγμα με τη scoped memory στην ουσία το type είναι encoded με δύο ξεχωριστούς τρόπους σε κείνη τη γραμμή, γιατί να χρειάζεται να τους ταιριάξεις σωστά?).

  • Moderators
Δημοσ.

παπι σ' ευχαριστώ που έγραψες το παράδειγμα (αν και ο κώδικας μου φαίνεται ύποπτα γνώριμος, δεν πιστεύω να τον πήρες απ' το SO :P ), αλλά νομίζω ότι ένας τέτοιος σχεδιασμός θα ήταν χειρότερος από έναν dedicated manager. Λόγω της φύσης του project, μπορεί να θέλω να κάνω preload πράγματα, μπορεί να θέλω κάποια πράγματα να μείνουν φορτωμένα μέχρι ένα συγκεκριμένο σημείο και μπορεί να θέλω κάποια πράγματα να γίνουν unload πριν τελειώσει το scope. Έτσι όπως το καταλαβαίνω, το factory είναι "δώσε μου κάτι (οτιδήποτε) να κάνω τη δουλειά μου και δε θέλω να ξανασχοληθώ με τη διαχείρισή του". Τα αντικείμενα διαχειρίζονται μόνα τους το lifecycle τους. Δε λέω ότι δε γίνεται να κάνω αυτό που θέλω με factory, αλλά νομίζω ότι είναι το λάθος εργαλείο γι' αυτή τη δουλειά. Και το κατσαβίδι είναι χρήσιμο και μπορείς να το χρησιμοποιήσεις για να καρφώσεις καρφιά, αλλά δε θα το προτιμήσεις απ' το σφυρί.

Δημοσ.

Το factory είναι κάπως παλιό (ίσως και ξεπερασμένο θα έλεγαν κάποιοι) pattern αλλά πάντα χρήσιμο όταν θέλεις να έχεις ένα σταθερό interface για την δημιουργία αντικειμένων που το είδος τους το μαθαίνεις στο run time (και με reflection κάνει ωραία δουλειά). 

 

 

My point; Μπορείς να επωφεληθείς πολύ από σωστή σχεδίαση αλλά για κάποιο λόγο την αποφεύγεις. Εάν έχεις κάποια assets τα οποία κάνουν κάτι, προφανώς καλούνται κάπου. Θέλεις να προσπελαύνεις και να δημιουργείς αυτά τα assets μέσω ενός κοινού μηχανισμού. 

 

Θα μπορούσα να προτείνω ένα google search με terms όπως:

 

Alternative to factory pattern

dependency injection

 

π.χ. ένα random αποτέλεσμα

 

http://tutorials.jenkov.com/dependency-injection/dependency-injection-replacing-factory-patterns.html

 

 

 

Υ.Γ. Γιατί τα αντικείμενα να ξέρουν μόνα τους τον κύκλο ζωής τους; Είναι δικό τους θέμα να το γνωρίζουν; 

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

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