Jump to content
  • Advertisement
Sign in to follow this  
ninmonkeys

const-ness and overloading error

This topic is 4590 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm writing a 3d vector class based on the book's source in 'physics for game developers' and various examples I'm finding on the web. I have a few questions: 1) I'm unsure when arguments, or functions should be 'const'. I've seen examples of the same function declared both ways. 1a) Are arguments declared as const if they are passed as a reference *and* are not modified? (If it's not a reference, it's implied/const, or I mean it can't be modified?) 1b) What determines if a function is declared const? If the function only returns a value, and doesn't actually modify and variables? 2) I'm unsure when I should use references for arguments or return values. I know I can if I want to modify the value in the function, but in some cases it seems optional, or chosen for speed? (I think it would be faster since it's passing a pointer rather than copying a type/class) Is it faster to return a reference rather than copy? 2b) Or is there another reason?
[source language="cpp"]
//const arg/function and referenced argurment
Vector3D operator -(void) const;
Vector3D& operator -=(const Vector3D& v);

//vs: no const argument/function and referenced argument
Vector3D operator -(void);
Vector3D& operator -=(Vector3D& v);

//vs: no const argument, no referenced argument
Vector3D& operator -=(Vector3D v);


3) Is this an error in the book source? Or is it valid to override an operator with 2 arguments? It tries to use 2 arguments for 'operator +' and 'operator -', but I can't get that to compile.
//Book 'broken' source for operator: +
Vector3d operator+(Vector3d u, Vector3d v) {
        return Vector3d(u.x+v.x, u.y+v.y, u.z+v.z);
}

//fixed? source for operator: +
Vector3d operator+(Vector3d u) {
	return Vector3d(this->x + u.x,
					this->y + u.y,
					this->z + u.z);
}

-- thanks, monkey

Share this post


Link to post
Share on other sites
Advertisement
Hi,

Const only really applies anywhere you want it to. It depends on what you are trying to achieve. I will answer your questions in typical uses.

1a) Arguments are passed by either const reference or a pointer of in nearly all cases. Passing by value is slow. You would pass as const reference if you wanted to effectively pass the variable in to a function but do not want the function to be able to modify the variable. This is the same as passing by value, except that passing by value takes more mempory because a separate copy of the passed value is stored on the stack.

1b) A function is declared const, as in:

int GetValue() const;

When the function does not modify the state of the class that it belongs to, it should be declared const. This is because when you make a const object of the type of this class, you can still call the const methods. You can not call a non-const method of a const object. Whereas if the object was non-const you could.

2a) The only times you should be returning a reference is when it is a reference to a parameter or an object on the heap. Otherwise you will cause undefined behaviour since you can never guarantee the contents of the unwound heap.

3) It's been a while so i will leave this to someone else.

Hope that helps,

Dave

Share this post


Link to post
Share on other sites
For 3, the two argument version would be if operator+ is defined outside the class. The one argument version is if is defined inside the class (notice the one argument version uses 'this' which only exists in a class method). So you want one or the other but probably not both.

If you're having trouble figuring out when to be put const on methods make a test app that passes around 'const Vector3D &' and try to do stuff with it. If the compiler complains about what you're doing it's either because you're trying to modify the internal state of the vector which you can't do if it's const or you forgot to stick a const on the end of the method declaration.

Share this post


Link to post
Share on other sites
Declare function arguments as const if you'd like to restrict the function from modifying them in any way.

Declare member functions as const if they do not modify class member data in any way. There is, however, another way to look at this issue: if the member function has a const outward appearance (to users of the class), but still modifies member data - like, for example, caching a private length variable in a size() member function - then the function is said to have conceptual constness and should be declared as such. Variables modified by the function - such as private member length should be qualified by mutable to allow the const member function to modify them.

3. It looks like the book is defining a free function operator overload, not a member function. Free function operators take 2 arguments (one for each operand).

For objects that can be used in arithmetic operations - such as your vector class - prefer to imitate the behaviour of built-in types. Create free-function operator overloads, especially if you require mixed-mode arithmetic - multiplying a vector by a scalar, for example.

In regards to constness, return by const value from arithmtic operators (+, *, etc.) to imitate the built-in types - this prevents accidental assignments to temporary objects created from these operators.

Share this post


Link to post
Share on other sites
1. Arguments should be declared const if they will not modified by the function. Usually arguments passed by value are not declared const because any changes would only affect the local copy anyway. Member functions should be declared const if they do not change the outward appearance of the object. For example:
class Shape
{

public:

// This function takes a parameter by const reference
// it does not need to change the shape it is passed, only
// obtain information from it.
// This function does not modify this shape, so it is declared
// const.
// This function returns a new shape representing the
// intersections of this shape and the given shape.
Shape intersect(Shape const & shape) const;

// This function sets the colour of this shape.
// The parameter will not be modified, only read. It is passed
// by value and so is not declared const, although it could have
// been declared const.
// Since this shape is modified the function is not declared
// const.
void setColour(Colour colour)
{
colour_ = colour;
}

// This function returns the area of this shape.
// It does not change the outward view of the object but it does
// change one of its member variables.
// This is done under the assumption that calculating the area
// of a shape is an expensive operation and should therefore be
// calculated only when needed but cached when calculated.
// Any operation which changes the area of the shape must set
// the area_ member back to a negative value.
// area_ must declared mutable so that it can be modified
// within a const function.
double area() const
{
if (area_ < 0)
{
area_ = computeArea();
}
return area_;
}

// This function modifies both this shape and the given shape
// so the parameter is passed by non-const reference and the
// function itself is not declared const
void collide(Shape & shape);

// other members

private:

Colour colour_;
mutable double area_;
// other members

};

Unfortunately C++ const is bitwise const, not logical const, which means that const member functions are not allowed to write to any member variables. The mutable keyword can be used to declare that a member variable is not part of the outward appearance of the class, as shown above.

2) Generally for parameters you should use const references by default with the exception of builtin types and small classes, which should be passed by value, objects which need to be modified by the function, which should be passed by non-const reference, and objects which might not exist, which should be passed by pointer (with or without const depending on whether or not they need to be modified).

For return values you should generally return by const reference when you can and by value when you have to. Use pointers when you might not have an actual object to return. You cannot return by reference or pointer if the object doesn't actually exist elsewhere:
class Stuff
{

public:

// Return by value is fine here
Thing function1()
{
return Thing();
}

// Error - you are returning a reference to something that no
// longer exists
Thing const & function1()
{
return Thing();
}

// Error - you are returning a pointer to something that no
// longer exists
Thing const * function3()
{
Thing thing;
return &thing;
}

// Technically OK - the Thing exists on the heap - but dangerous
// who is responsible for deleting the Thing?
Thing const * function4()
{
return new Thing();
}

// Return by value is fine here
Thing function5()
{
return thing_;
}

// Return by const reference is fine here and may be faster
Thing const & function6()
{
return thing_;
}

// Return by non-const reference is dangerous - we just made
// our private variable public!
Thing & function7()
{
return thing_;
}

// return by pointer-to-const is dangerous because somebody may
// try to delete it!
Thing const * function8()
{
return &thing_;
}

private:

Thing thing_;

};

3. operatorX() may either be a member function or a free function. If it is a member then it takes one parameter and this acts as the other parameter. If it is a free function then it takes two parameters. Generally prefer to make operatorX() a free function implemented in terms of the member function operatorX=():
class PointlessIntegerWrapper
{

public:

PointlessIntegerWrapper(int i)
:
i_(i)
{
}

// take by const reference, return self by non-const reference.
PointlessIntegerWrapper & operator+=(PointlessIntegerWrapper const & piw)
{
i_ += piw.i_;
return *this;
}

private:

int i_;

};

// take first parameter by value (we need to create a new PointlessIntegerWrapper
// object and this is a nice way of doing so).
// take second parameter by const reference (since we don't modify it).
// return by value (We can't return a reference because the returned object
// doesn't exist outside of this function).
PointlessIntegerWrapper operator+(PointlessIntegerWrapper lhs, PointlessIntegerWrapper const & rhs)
{
return lhs += rhs;
}

Enigma

Share this post


Link to post
Share on other sites
Quote:
Original post by stylin
In regards to constness, return by const value from arithmtic operators (+, *, etc.) to imitate the built-in types - this prevents accidental assignments to temporary objects created from these operators.

Although this is a good point to bring up there are some advantages to returning by non-const value:
Quote:
C++ Coding Standards by Herb Sutter & Andrei Alexandrescu, Item 27
Another variation is to have operator@ return a const value. This technique has the advantage that it disables nonsensical code such as a + b = c, but it does so at the cost of disabling some potentially useful constructs such as a = (b + c).replace(pos, n, d) - expressive code that, in one shot, concatenates string b and c, replaces some characters, and assigns the final result to a.

It's an issue on which I don't have a clear preference and where I currently recommend examining on a case-by-case basis (but often forget to do so myself [looksaround]).

Enigma

Share this post


Link to post
Share on other sites
Quote:
Original post by Enigma
It's an issue on which I don't have a clear preference and where I currently recommend examining on a case-by-case basis ...


Great heads up, but for simple vector classes, I think returning const-values is correct. I can't think of any time you'd like to modify the result of a vector expression. Member (helper) functions such as Normal(), for instance, are const, and may be better extracted to a free function anyway. If I'm overlooking something obvious - or preferred - please let me know.

Share this post


Link to post
Share on other sites
Quote:
Original post by stylin
For objects that can be used in arithmetic operations - such as your vector class - prefer to imitate the behaviour of built-in types. Create free-function operator overloads, especially if you require mixed-mode arithmetic - multiplying a vector by a scalar, for example.


What is the reason to declare all the overloaded operators as free functions? I'm wondering since it sounds like enigma said to do it another way. (Assuming I understand enigma right: create operatorX=() as members, and the rest of the operatorX()'s as free functions that use the operatorX=() operators)

Quote:
Original post by stylin
In regards to constness, return by const value from arithmtic operators (+, *, etc.) to imitate the built-in types - this prevents accidental assignments to temporary objects created from these operators.
Could you give me an example of how you could have an accidental assignment of a temperary object?

Currently none of my arithmetic operators return a constant value, since I'm still figuring out if something like this would be of any use: v1 = (v2+v3).Normalize(); will be of any use or not.

Quote:
Original post by: Enigma
2) Generally for parameters you should use const references by default with the exception of builtin types and small classes, which should be passed by value, objects which need to be modified by the function, which should be passed by non-const reference, and objects which might not exist, which should be passed by pointer (with or without const depending on whether or not they need to be modified).
Why pass a small class by value? (I thought pointers were always faster to pass than by value?)

Quote:
Original post by: Enigma
3. operatorX() may either be a member function or a free function. If it is a member then it takes one parameter and this acts as the other parameter. If it is a free function then it takes two parameters. Generally prefer to make operatorX() a free function implemented in terms of the member function operatorX=():
Does that mean make operatorX=() functions members, and the rest as free functions? What is the reasoning for this?

The possible reason I ran into was right now all of mine are member functions, and I could declare "vecor * scalar" but not "scalar * vector". Which I'm not sure if it is my fault, or that it has to be a free function to declare both?

Thanks again.

Share this post


Link to post
Share on other sites
stylin: Yes, you're almost certainly right regards constness or return values from arithmetic operators for a simple vector. I only brought the point up because a) your advice sounded more general than that and b) having written that long post I'd forgotten what the specific case that had triggered the discussion was!

ninmonkeys:
Quote:
Original post by ninmonkeys
Quote:
Original post by stylin
For objects that can be used in arithmetic operations - such as your vector class - prefer to imitate the behaviour of built-in types. Create free-function operator overloads, especially if you require mixed-mode arithmetic - multiplying a vector by a scalar, for example.


What is the reason to declare all the overloaded operators as free functions? I'm wondering since it sounds like enigma said to do it another way. (Assuming I understand enigma right: create operatorX=() as members, and the rest of the operatorX()'s as free functions that use the operatorX=() operators)

I said in general to make operatorX=() a member function. Unfortunately I forgot to mention the main exception to this advice, which is if you can trivially make it a free function then you may want to do so. You can trivially make operatorX=() a free function if there are no private or protected data members in the classes interface that will require operating on, i.e.:
struct TrivialVector
{

// all members are public so operator+= can trivially be made a free
// function.
float x, y, z;

};

TrivialVector & operator+=(TrivialVector & lhs, TrivialVector const & rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
lhs.z += rhs.z;
}

class NormalisedVector
{

public:

// member functions

private:

// data members are now private since we must maintain an
// invariant over them (x² + y² + z² == 1).
// operator+= cannot trivially be made a free function - to do
// so would require either declaring it a friend or implementing
// it in terms of another member function.
float x, y, z;

};

Some people may disagree and prefer to always make operatorX=() a free function and make it a friend when neccessary. I personally prefer to avoid unneccessary friendships and always make operatorX=() a member function for consistency. This is mostly an issue of personal preference though.

Quote:
Quote:
Original post by stylin
In regards to constness, return by const value from arithmtic operators (+, *, etc.) to imitate the built-in types - this prevents accidental assignments to temporary objects created from these operators.
Could you give me an example of how you could have an accidental assignment of a temperary object?

Currently none of my arithmetic operators return a constant value, since I'm still figuring out if something like this would be of any use: v1 = (v2+v3).Normalize(); will be of any use or not.

In that example Normalize ought to be a const member function, so returning a const value would still allow such a construction to work, as stylin pointed out.

Quote:
Quote:
Original post by: Enigma
2) Generally for parameters you should use const references by default with the exception of builtin types and small classes, which should be passed by value, objects which need to be modified by the function, which should be passed by non-const reference, and objects which might not exist, which should be passed by pointer (with or without const depending on whether or not they need to be modified).
Why pass a small class by value? (I thought pointers were always faster to pass than by value?)

It will be no slower to pass a small value than a pointer and passing by value eliminates a dereference on access and improves locality of data. It's a minor point and not worth spending too much time over. Pick reasonable defaults and if a profiler eventually demonstrates that you made the wrong choice it should be trivial to change it.

Quote:
Quote:
Original post by: Enigma
3. operatorX() may either be a member function or a free function. If it is a member then it takes one parameter and this acts as the other parameter. If it is a free function then it takes two parameters. Generally prefer to make operatorX() a free function implemented in terms of the member function operatorX=():
Does that mean make operatorX=() functions members, and the rest as free functions? What is the reasoning for this?

It means you only implement the operator in one place, so any maintenance only has to be done in one place. For example imagine you have a three component (x, y & z) vector class with operator+ and operator+=() both independantly implemented. If you decide to change your vector to four components (x, y, z & w) then you must rewrite two operators. By implementing operator+() in terms of operator+= you only have to update one operator. It may seem like a small saving, but when you start overloading more operators in more complex classes the work saved soon adds up. Membership has already been discussed.

Quote:
The possible reason I ran into was right now all of mine are member functions, and I could declare "vecor * scalar" but not "scalar * vector". Which I'm not sure if it is my fault, or that it has to be a free function to declare both?

It has to be a free function to gain implicit type conversion on both arguments. C++ will implicitly convert types in parameter lists if it can, but will never implicitly convert an object so that it can call a member function on it. So in order to get implicit type conversion on the left hand argument you must implement the operator as a free function. Note that operatorX=() never requires type conversion on its left hand argument.

Enigma

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!