2015-09-07 3 views
1

J'ai récemment trébuché sur des modèles d'expression en C++. Il y a une chose que je ne comprends pas vraiment à propos de leur implémentation, et c'est pourquoi la classe de base est nécessaire (à partir de laquelle tous les autres objets relatifs à l'expression de modèle dérivent d'une manière CRTP). Un exemple simple qui fait l'addition et la multiplication scalaire sur les vecteurs (objets de type Vec, sans classe de base):Modèles d'expression C++ - Pourquoi la classe de base?

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

class Vec 
{ 
    vector<double> data; 
public: 

    template<typename E> 
    Vec(E expr) 
    { 
     data = vector<double>(expr.size()); 
     for (int i = 0; i < expr.size(); i++) 
      data[i] = expr[i]; 
    } 
    Vec(int size) 
    { 
     data = vector<double>(size); 
     for (int i = 0; i < size; i++) 
      data[i] = i; 
    } 
    double operator [] (int idx) { 
     return data[idx]; 
    } 

    int size() { return data.size(); } 

    bool operator < (Vec &rhs) 
    { 
     return (*this)[0] < rhs[0]; 
    } 

    bool operator > (Vec &rhs) 
    { 
     return (*this)[0] > rhs[0]; 
    } 

}; 

template<typename E1, typename E2> 
class VecAdd 
{ 
    E1 vec_expr1; 
    E2 vec_expr2; 

public: 
    VecAdd(E1 vec_expr1, E2 vec_expr2) : vec_expr1(vec_expr1), vec_expr2(vec_expr2) 
    {} 

    double operator [] (int idx) { return vec_expr1[idx] + vec_expr2[idx]; } 
    int size() { return vec_expr1.size(); } 
}; 

template<typename E> 
class ScalarMult 
{ 
    E vec_expr; 
    double scalar; 

public: 
    ScalarMult(double scalar, E vec_expr) : scalar(scalar), vec_expr(vec_expr) 
    {} 

    double operator [] (int idx) { return scalar*vec_expr[idx]; } 
    int size() { return vec_expr.size(); } 
}; 

template<typename E1, typename E2> 
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2) 
{ 
    return VecAdd<E1, E2>(vec_expr1, vec_expr2); 
} 

template<typename E> 
ScalarMult<E> operator * (double scalar, E vec_expr) 
{ 
    return ScalarMult<E>(scalar, vec_expr); 
} 

int main() 
{ 
    Vec term1(5); 
    Vec term2(5); 

    Vec result = 6*(term1 + term2); 
    Vec result2 = 4 * (term1 + term2 + term1); 

    //vector<Vec> vec_vector = {result, result2};  does not compile 
    vector<Vec> vec_vector; 

    vec_vector = { result2, result }; //compiles 

    vec_vector.clear(); 
    vec_vector.push_back(result); 
    vec_vector.push_back(result2);  //all this compiles 

    for (int i = 0; i < result.size(); i++) 
     cout << result[i] << " "; 
    cout << endl; 

    system("pause"); 

    return 0; 
} 

Le code ci-dessus compiles (sauf pour la ligne indiquée), et il évalue les expressions simples dans la principale fonction sans faute. Si les expressions sont affectées à un objet de type Vec et affectent leur contenu à un objet Vec, se détruisant dans le processus dans tous les cas, pourquoi est-il nécessaire pour une classe de base? (Comme indiqué dans l'article this Wikipedia)

EDIT:

Je sais que ce code est un peu en désordre et mauvaise (copie ou inutiles, etc.), mais je ne prévois pas d'utiliser ce code spécifique. Ceci est juste pour illustrer que les modèles d'expression fonctionnent dans cet exemple sans la classe de base CRTP - et j'essaie de comprendre exactement pourquoi cette classe de base est nécessaire.

+0

Qu'est-ce que 'E expr'? S'il s'agit d'une structure de données, pourquoi la copiez-vous en tant que paramètre, puis de nouveau dans la fonction? Pourquoi ne pas utiliser le redimensionnement ou la fonction d'assignation (en fonction des types valides de E) dans la fonction? Pourquoi ne pas utiliser 'vector :: size_type' pour stocker le type du vecteur? Je ne comprends pas la logique de vos opérateurs < >. Vector prend déjà en charge < > opérateurs pour les comparaisons lexicographiques. –

+0

S'il vous plaît voir mon edit –

+1

Votre 'opérateur +' correspond, eh bien, à propos de tout sous le soleil. –

Répondre

3

Votre

template<typename E1, typename E2> 
VecAdd<E1, E2> operator + (E1 vec_expr1, E2 vec_expr2) 

correspondent à tous les types définis par l'utilisateur, non seulement les types d'expression. Lorsqu'il est instancié avec des types non vectoriels, il échouera probablement. Cela interagit très mal avec d'autres types C++, pouvant inclure des types de bibliothèques standard, qui fournissent leur propre operator + personnalisé et peuvent dépendre de correspondances inexactes résolvant leurs propres operator + après des conversions implicites.

La création de operator + uniquement disponible pour VecExpression<E> permet d'éviter ce problème.

+0

Ok, c'est logique. Donc, c'est la raison principale de la classe de base dans à peu près tous les exemples que je vois sur les modèles d'expression? Savez-vous pourquoi cette ligne sur laquelle j'essaie d'initialiser le vecteur avec une liste d'initialisation ne compile pas? –

+0

@KonradKapp Cela compile très bien pour moi avec GCC et avec clang. Je ne vois pas non plus un problème immédiat qui pourrait ne faire surface que dans d'autres compilateurs, mais je néglige peut-être quelque chose de simple. – hvd

+0

ok, merci quand même. J'utilise Visual Studio 2013. Mais de toute façon, la raison principale pour utiliser la classe de base est celle que vous avez mentionnée dans votre réponse d'origine (donc elle ne correspond à aucun autre type de données)? –