tags:

views:

578

answers:

8

I'd like to know some best practice when designing c++ classes.

To put it in context, I have a c++ class named Vec3.

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){...}
};

So, I have this class that does computing over a Vector of size 3. I'd like to know what's better. Working with pointers or not.

Should I return pointer and have my parameters as Pointers or not.

Vec3 add(Vec3 v1) or Vec3* add(Vec3 v1) or Vec3* add(Vec3* v1) or ....

Now I'm confused, I don't know if I should use pointer or not in my class. I guess there is always a way to send my arguments to function that don't handle pointers...

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

And there is a solution that is probably the best of all I can come up with.. having both functions

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

But I fear this will lead to duplicate code and may be overhead.

Thank you for answers...btw, I could use a template to change the size of the Vector but I prefer to keep my Vec3 class alone and create a Vec4 class or name it Quaternion.

EDIT Here is the solution I came with. Feel free to comment or modify or reuse the code. One thing. I just want to mention that, in my case, This class is supposed to be transparent. Just like we add numbers.

int i = 10;
int j = 15;
int k = i + k;

If the add overload modify the object that is calling the function in this case i. I would endup with a k being a reference to i and i being equal to 25. But what we really want here is a k equal to 25 and i,k unchanged.

Thats how my class work. Vec3 k = i + k will not modify i or k because we are creating a new number from these values. The only case where I return a reference is for +=, -=, ++, --..., set([XYZ])? and normalize.

It could be fun to do something like myvec.setX(10).normalize().scale(10)

NOTE: scale should return a reference. I didn't see it but I guess it should be better this way.

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

http://pastebin.com/f413b7ffb

Thank you all, I'll be working on the Matrix class now.

+2  A: 

Since the int's are primtives, leave them as is. for anything with vec3's use references.

eg.

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

In C you'd use a pointer, but in c++ a reference is usually better for objects.

sfossen
+4  A: 

These are the rules I usually stick to. Note 'usually', sometimes there are reasons for doing things differently...

For parameters I don't intend to modify I pass by value if they aren't too large since they will be copied. If they are a bit large or aren't copyable, you could use a const reference or a pointer (I prefer const reference).

For parameters I do intend to modify, I use a reference.

For return values I will return a copy whenever possible. Some times it's handy to return a reference (this works well for a single function for get/set where you don't need to do any special processing when the item is fetched or set).

Where pointers really shine in my opinion is for instance variables where I want control over when it is constructed or destructed.

Hope that helps.

Arnold Spence
It's exacly what I needed. Thank you. I guess I should make more use of references.:)
Sybiam
+1  A: 

If you implement operators like operator+=() and operator*=(), you'll want it to return *this as Vec3&.

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

For other basic operators like operator+() and your add() you will want to return a copy:

Vec3 operator+(const Vec3& v2) {
    Vec3 ret;
    // add
    return ret;
}
greyfade
A: 

As greyfade mentioned, you should be concerned about copy semantics. In this case, you should add these methods as well:

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
  }
};
Tommy Hui
Simon Buchan
You don't need to write your own copying functions for classes which use std containers or pod values - let the compiler do it for you.
Pete Kirkham
A: 
E Dominique
returning `*this` allows method chaining: `v.add(Vec(1,2,3)).multiply(4);`. Of course, using operator overloading is somewhat nicer, excepting precedance: `(v += Vec(1,2,3)) *= 4;`. op+ and friends should have copy semantics, though, which hurts efficiency.
Simon Buchan
The add function shouldn't modify the object. So I need to return a copy. It's like writting j = i + k; which i and k have a value of 10 and I would end up with i having a value of 20. It just doesn't make sense.
Sybiam
@Sybiam I don't agree. An operator add() should indeed modify the object. If you overload operators, though, copy semantics should be implemented (as Simon pointed out). If you want to implement "j = i + k" without op+, you should write Vec3 j(i); j.add(k);
E Dominique
A: 

This page contains a really good discussion on this subject: http://www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html

It would be a good discussion, if only it wasn't advocating for the self-assignment anti-pattern. Defining binary operators as member functions is not state of the art either.
Luc Hermitte
A: 

You almost certainly don't want the parameters be pointers in that case. Consider this example for why:

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

For references to constant that is no problem. You could easily nest operations even:

// declaration: Vec3 add(Vec3 const& v);
v2.add(v1.add(v3));
Johannes Schaub - litb
+2  A: 

Vectors have known semantics (known for you and the users of your class), so I would consider overloading the operators (+, -, +=, -=) in doing so, I would use the regular definitions rather than changing them:

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

I would avoid using pointers. References are more natural, and there is only a very small set of situations were you do need them instead of references / values. Avoid duplicating your functions (with/without pointers) as that will make your code more complex unnecessarily, as you already posted in the question, you can always dereference a pointer to retrieve a reference.

Offer both a constant and a non-constant operator[]:

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: I forgot to mention about the size_t detail: Prefer using unsigned / size_t for index parameters instead of signed integers.

David Rodríguez - dribeas
This is indeed the way to go most of the time. BTW, the const subscript operator could return a const reference (which may offer some interesting side effects) ; and some people prefer return a "const T" by value instead of just "T", see GOTW on this subject.
Luc Hermitte
"Most of the time": sometimes it is easier to implement the x= operator in terms of the x operator. It is the case with matrices * and *= operators.
Luc Hermitte
It make a lot of sense. There is one thing I'm not sure to understand. If i have a const and non const function. How is the compiler going to know which one to use? But right now, i'll keep the other function and not just operators as I need them to make the refactoring from C to C++ less hard.
Sybiam
When there is a const and a non-const overload, the compiler will always choose the non-const function, unless the object is const at the time/point/scope (sorry, I can't find the right word) the function is called. See http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.12
Luc Hermitte
@Luc Hermite: I don't know the particular case with Matrices, but it is more common to define operator+ in terms of operator*=:X tmp += b; return tmp; }
David Rodríguez - dribeas