2009-03-09 6 views
4

J'aimerais connaître les meilleures pratiques lors de la conception de classes C++. Pour mettre le contexte en contexte, j'ai une classe C++ nommée Vec3.Meilleures pratiques de classe C++

class Vec3{ 
private: 
    float elements[3]; 
public: 
    Vec3(Vec3 v1){...} 
    Vec3(int x, int y, int z){...} 
    Vec3 add(Vec3 v1){...} 
    Vec3 add(int x, int y, int z){...} 
    ... 
    Vec3 multiply(Vec3 v1){...} 
    ... 
    int dotProduct(Vec3 v1){...} 
    Vec3 normalize(){...} 
    .... 
    int operator[](int pos){...} 
}; 

Donc, j'ai cette classe qui fait le calcul sur un vecteur de taille 3. Je voudrais savoir ce qui est mieux. Travailler avec des pointeurs ou non. Dois-je retourner le pointeur et avoir mes paramètres comme pointeurs ou non.

vec3 ajouter (vec3 v1) ou vec3 * ajouter (vec3 v1) ou vec3 * ajouter (vec3 * v1) ou maintenant ....

Je suis confus, je ne sais pas si je devrais utilisez un pointeur ou pas dans ma classe. Je suppose qu'il ya toujours un moyen d'envoyer mes arguments à la fonction qui ne gère pas les pointeurs ...

Vec3* v2 = new Vec3(1,1,1); 
Vec3 sum = v1.add(*v2); 

Et il y a une solution qui est probablement le meilleur de tout ce que je peux venir avec .. ayant les deux fonctions

Vec3 add(Vec3 v2){...} 
Vec3* add(Vec3* v2){...} 

Mais je crains que cela conduise à un code en double et peut être en surcharge.

Merci pour les réponses ... btw, je pourrais utiliser un template pour changer la taille du Vector mais je préfère garder ma classe Vec3 seule et créer une classe Vec4 ou la nommer Quaternion.

EDIT Voici la solution que je suis venu avec. N'hésitez pas à commenter ou modifier ou réutiliser le code. Une chose. Je veux juste mentionner que, dans mon cas, cette classe est supposée être transparente. Tout comme nous ajoutons des chiffres. Si la surcharge d'ajout modifie l'objet appelant la fonction dans ce cas, i. Je finirais avec un k étant une référence à i et i étant égal à 25. Mais ce que nous voulons vraiment ici est un k égal à 25 et i, k inchangé.

Voilà comment fonctionne ma classe. Vec3 k = i + k ne modifiera ni i ni k car nous créons un nouveau nombre à partir de ces valeurs. Le seul cas où je renvoie une référence est + =, - =, ++, --..., set ([XYZ])? et normaliser.

Il pourrait être amusant de faire quelque chose comme myvec.setX (10) .normalize() échelle (10)

NOTE:. Échelle doit renvoyer une référence. Je ne l'ai pas vu mais je suppose que ça devrait être mieux ainsi.

Vec3 t = myvec.normalize().scale(100).copy(); 

http://pastebin.com/f413b7ffb

Merci, je vais travailler sur la classe Matrix maintenant.

Répondre

8

Voici les règles auxquelles je suis habitué. Notez 'habituellement', parfois il y a des raisons de faire les choses différemment ...

Pour les paramètres que je n'ai pas l'intention de modifier je passe par la valeur s'ils ne sont pas trop grands puisqu'ils seront copiés. Si elles sont un peu grandes ou ne sont pas copiables, vous pouvez utiliser une référence const ou un pointeur (je préfère la référence const).

Pour les paramètres que j'ai l'intention de modifier, j'utilise une référence.

Pour les valeurs de retour, je retournerai une copie lorsque cela est possible. Il est parfois utile de renvoyer une référence (cela fonctionne bien pour une seule fonction pour get/set, où vous n'avez pas besoin d'effectuer de traitement spécial lorsque l'élément est récupéré ou défini).

Où les pointeurs brillent vraiment à mon avis est par exemple des variables où je veux contrôler quand il est construit ou détruit.

Espérons que ça aide.

+0

C'est exactement ce dont j'avais besoin. Je vous remercie. Je devrais utiliser davantage de références. :) –

3

Puisque les int sont des primitifs, laissez-les tels quels. pour quoi que ce soit avec les références d'utilisation de vec3.

par ex.

Vec3 add(const Vec3 &v1){...} 

En C, vous devriez utiliser un pointeur, mais C++ une référence est généralement mieux pour les objets.

1

Si vous implémentez des opérateurs tels que operator+=() et operator*=(), vous devez indiquer *this comme Vec3&.

Vec3& operator+=(const Vec3& v2) { 
    // add op 
    return *this; 
} 

Pour d'autres opérateurs de base comme operator+() et votre add() vous voulez retourner une copie :

Vec3 operator+(const Vec3& v2) { 
    Vec3 ret; 
    // add 
    return ret; 
} 
-2

Comme greyfade mentionné, vous devriez être préoccupé par la sémantique de copie. Dans ce cas, vous devez ajouter ces méthodes ainsi:

class Vec3 { 
public: 
    Vec3(const Vec3& rhs) { 
    copy(rhs); 
    } 

    Vec3 operator=(const Vec3& rhs) { 
    copy(rhs); 
    return *this; 
    } 

private: 
    void copy(const Vec3& rhs) { 
    // copy state from rhs 
    } 
}; 
+0

op = devrait retourner Vec3 &, pas Vec3 –

+1

Vous n'avez pas besoin d'écrire vos propres fonctions de copie pour les classes qui utilisent des conteneurs std ou des valeurs de pod - laissez le compilateur le faire pour vous . –

0

Il n'y a pas besoin d'avoir les arguments comme des pointeurs dans ce cas, et vous devriez vraiment pas retourner un nouvel objet pour tous les opérateurs comme ça.
Dans la programmation OO, l'idée est d'opérer sur l'objet réel, par ex. ayant

 

void add(Vec3 v1); 
void multiply(Vec3 v1); 

Je dirais aussi que vous devriez en tenir à prendre des objets vec3 comme arguments (et non x, y, z). Si vous avez seulement x, y, z, vous pouvez appeler add (Vec3 (x, y, z)).

+0

renvoyer '* this' permet de chaîner la méthode:' v.add (Vec (1,2,3)) .mulply (4); '. Bien sûr, l'utilisation de la surcharge de l'opérateur est plus agréable, à l'exception de la précédence: '(v + = Vec (1,2,3)) * = 4;'. op + et ses amis devraient avoir une sémantique de copie, ce qui nuit à l'efficacité. –

+0

La fonction d'ajout ne doit pas modifier l'objet. Donc j'ai besoin de retourner une copie. C'est comme écrire j = i + k; ce que je et k ont ​​une valeur de 10 et je finirais avec i ayant une valeur de 20. Cela n'a tout simplement pas de sens. –

+0

@Sybiam Je ne suis pas d'accord. Un opérateur add() devrait en effet modifier l'objet. Si vous surchargez les opérateurs, cependant, la sémantique de copie devrait être implémentée (comme Simon l'a souligné). Si vous voulez implémenter "j = i + k" sans op +, vous devez écrire Vec3 j (i); j.add (k); –

1

Vous presque certainement ne veulent pas que les paramètres soient des pointeurs dans ce cas. Considérez cet exemple pourquoi:

// Error: not possible to take the address of the temporary 
//  return value. 
v2.add(&someFunctionReturningVec3()); 

Pour les références à constante qui est sans aucun problème.Vous pouvez facilement les opérations de nid même:

// declaration: Vec3 add(Vec3 const& v); 
v2.add(v1.add(v3)); 
3

vecteurs ont connu la sémantique (connus pour vous et les utilisateurs de votre classe), donc j'envisageraient de surcharger les opérateurs (+, -, + =, - =) à faire donc, j'utiliserais les définitions normales plutôt que de les changer:

// instead of add: 
class Vec3 { 
public: 
    Vec3& operator+=(Vec3 const & rhs); 
}; 
// implemented as free function: 
Vec3 operator+(Vec3 const &lhs, Vec3 const & rhs); 

J'éviterais d'utiliser des pointeurs. Les références sont plus naturelles, et il n'y a qu'un très petit nombre de situations où vous en avez besoin au lieu de références/valeurs. Évitez de dupliquer vos fonctions (avec/sans pointeurs) car cela rendrait votre code plus complexe inutilement, comme vous l'avez déjà posté dans la question, vous pouvez toujours déréférencer un pointeur pour récupérer une référence.

offre à la fois une constante et un opérateur non constant []:

class Vec3 { 
public: 
    float operator[](size_t pos) const; // returns copy, data does not change 
    float& operator[](size_t pos); // returns a reference and allows changing the contents 
}; 

EDIT: J'ai oublié de mentionner sur les détails size_t: Préférez l'utilisation non signé/size_t pour les paramètres d'index au lieu des entiers signés.

+0

C'est en effet le chemin à parcourir la plupart du temps. BTW, l'opérateur de l'indice const pourrait renvoyer une référence const (qui peut offrir des effets secondaires intéressants); et certaines personnes préfèrent renvoyer un "const T" par valeur au lieu de simplement "T", voir GOTW sur ce sujet. –

+0

"La plupart du temps": il est parfois plus facile d'implémenter l'opérateur x = en termes d'opérateur x. C'est le cas des matrices * et * = opérateurs. –

+0

Cela a beaucoup de sens. Il y a une chose que je ne suis pas sûre de comprendre. Si j'ai une fonction const et non const. Comment le compilateur va-t-il savoir lequel utiliser? Mais maintenant, je garderai l'autre fonction et pas seulement les opérateurs car j'en ai besoin pour rendre le refactoring de C à C++ moins dur. –