something-from-vector-implementation

Keywords: STL

This article is for the readers who have a basic background of math, graphics, c++. Although I have written the code about Vector2,Vector3 by c++, but as time goes by, all the principles were forgotten. So I decided to write something about the simple program to get much more strong memory.
If you search the implementation of vector in github, you can find lots examples. Here’s an example vector.hpp, actually it’s too hard for me to read. Today’s vector.hpp is about the really basic syntax in c++ such as operator overloading, friends, inline etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#pragma once

#include <cmath>
#include <iostream>

class Vector3f
{
public:
Vector3f()
: x(0)
, y(0)
, z(0)
{}
Vector3f(float xx)
: x(xx)
, y(xx)
, z(xx)
{}
Vector3f(float xx, float yy, float zz)
: x(xx)
, y(yy)
, z(zz)
{}
Vector3f operator*(const float& r) const
{
return Vector3f(x * r, y * r, z * r);
}
Vector3f operator/(const float& r) const
{
return Vector3f(x / r, y / r, z / r);
}

Vector3f operator*(const Vector3f& v) const
{
return Vector3f(x * v.x, y * v.y, z * v.z);
}
Vector3f operator-(const Vector3f& v) const
{
return Vector3f(x - v.x, y - v.y, z - v.z);
}
Vector3f operator+(const Vector3f& v) const
{
return Vector3f(x + v.x, y + v.y, z + v.z);
}
Vector3f operator-() const
{
return Vector3f(-x, -y, -z);
}
Vector3f& operator+=(const Vector3f& v)
{
x += v.x, y += v.y, z += v.z;
return *this;
}
friend Vector3f operator*(const float& r, const Vector3f& v)
{
return Vector3f(v.x * r, v.y * r, v.z * r);
}
friend std::ostream& operator<<(std::ostream& os, const Vector3f& v)
{
return os << v.x << ", " << v.y << ", " << v.z;
}
float x, y, z;
};

class Vector2f
{
public:
Vector2f()
: x(0)
, y(0)
{}
Vector2f(float xx)
: x(xx)
, y(xx)
{}
Vector2f(float xx, float yy)
: x(xx)
, y(yy)
{}
Vector2f operator*(const float& r) const
{
return Vector2f(x * r, y * r);
}
Vector2f operator+(const Vector2f& v) const
{
return Vector2f(x + v.x, y + v.y);
}
float x, y;
};

inline Vector3f lerp(const Vector3f& a, const Vector3f& b, const float& t)
{
return a * (1 - t) + b * t;
}

inline Vector3f normalize(const Vector3f& v)
{
float mag2 = v.x * v.x + v.y * v.y + v.z * v.z;
if (mag2 > 0)
{
float invMag = 1 / sqrtf(mag2);
return Vector3f(v.x * invMag, v.y * invMag, v.z * invMag);
}

return v;
}

inline float dotProduct(const Vector3f& a, const Vector3f& b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}

inline Vector3f crossProduct(const Vector3f& a, const Vector3f& b)
{
return Vector3f(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}

Scan the program above, here’s some tips:

  • Inline Functions

  • Reference Variables

  • Operator Overloading

  • Friends

Inline Functions

Inline functions are a C++ enhancement designed to speed up programs.

The primary distinction between normal functions and inline functions is not in how you code them but in how the C++ compiler incorporates them into a program.To understand the distinction between inline functions and normal functions,you need to peer more deeply into a program’s innards.Let’s do that now.

How do normal function calls work?

The final product of the compilation process is an executable program,which consists of a set of machine language instructions.When you start a program,the operating system loads these instructions into the computer’s memory so that each instruction has a particular memory address.The computer then goes through these instructions step-by-step.Sometimes,as when you have a loop or a branching statement,program execution skips over instructions,jumping backward or forward to a particular address. Normal function calls also involve having a program jump to another address (the function’s address) and then jump back when the function terminates.
Let’s look at a typical implementation of that process in a little more detail.When a program reaches the function call instruction, the program stores the memory address of the instruction immediately following the function call,copies function arguments to the stack (a block of memory reserved for that purpose),jumps to the memory location that marks the beginning of the function,executes the function code (perhaps placing a return value in a register),and then jumps back to the instruction whose address it saved. Jumping back and forth and keeping track of where to jump means that there is an overhead in elapsed time to using functions.

How do inline functions work?

C++ inline functions provide an alternative.In an inline function,the compiled code is “in line” with the other code in the program.That is,the compiler replaces the function call with the corresponding function code.With inline code,the program doesn’t have to jump to another location to execute the code and then jump back.

Inline functions thus run a little faster than regular functions,but they come with a memory penalty.If a program calls an inline function at ten separate locations,then the program winds up with ten copies of the function inserted into the code.

inline-functions-versus-regular-functions(from[1])

When use inline?

You should be selective about using inline functions.If the time needed to execute the function code is long compared to the time needed to handle the function call mechanism,then the time saved is a relatively small portion of the entire process.If the code execution time is short,then an inline call can save a large portion of the time used by the non-inline call.On the other hand,you are now saving a large portion of a relatively quick process,so the absolute time savings may not be that great unless the function is called frequently.

Inline versus Macros

The inline facility is an addition to C++. C uses the preprocessor #define statement to provide macros, which are crude implementations of inline code. For example, here’s a macro for squaring a number:

1
#define SQUARE(X) X*X

This works not by passing arguments but through text substitution, with the X acting as a symbolic label for the “argument”:

1
2
3
a = SQUARE(5.0); is replaced by a = 5.0*5.0;
b = SQUARE(4.5 + 7.5); is replaced by b = 4.5 + 7.5 * 4.5 + 7.5;
d = SQUARE(c++); is replaced by d = c++*c++;

Only the first example here works properly. You can improve matters with a liberal application of parentheses:

1
#define SQUARE(X) ((X)*(X))

Still, the problem remains that macros don’t pass by value. Even with this new definition, SQUARE(c++) increments c twice, but the inline square() function evaluates c , passes that value to be squared, and then increments c once. The intent here is not to show you how to write C macros.

Rather, it is to suggest that if you have been using C macros to perform function-like services, you should consider converting them to C++ inline functions.

Reference Variables

The main use for a reference variable is as a formal argument to a function.If you use a reference as an argument,the function works with the original data instead of with a copy.References provide a convenient alternative to pointers for processing large structures with a function,and they are essential for designing classes.

Creating a reference variable

‘&’ in c++ has two functions:

  • to indicate the address of a variable

  • to declare references

1
2
int rats;
int & rodents = rats; // makes rodents an alias for rats

In this context, & is not the address operator.Instead,it serves as part of the type identifier.

Just as char * in a declaration means pointer-to-char , int & means reference-to-int .The reference declaration allows you to use rats and rodents interchangeably;both refer to the same value and the same memory location.

Difference between pointer and reference?

1
2
3
int rats = 101;
int & rodents = rats; // rodents a reference
int * prats = &rats; // prats a pointer

Then you could use the expressions rodents and *prats interchangeably with rats and use the expressions &rodents and prats interchangeably with &rats .From this standpoint,a reference looks a lot like a pointer in disguised notation in which the * dereferencing operator is understood implicitly.And,in fact,that’s more or less what a reference is.But there are differences besides those of notation.For one,it is necessary to initialize the reference when you declare it;you can’t declare the reference and then assign it a value later the way you can with a pointer:

1
2
3
int rat;
int & rodent;
rodent = rat; // No, you can't do this.

You should initialize a reference variable when you declare it.

A reference is rather like a const pointer;you have to initialize it when you create it, and when a reference pledges its allegiance to a particular variable,it sticks to its pledge. That is,

int & rodents = rats; (equals to) int * const pr = & rats;

References as Function Parameters

code from[1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;

void swapr(int & a, int & b); // a, b are aliases for ints
void swapp(int * p, int * q); // p, q are addresses of ints
void swapv(int a, int b); // a, b are new variables

int main()
{

int wallet1 = 300;
int wallet2 = 350;

cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;

cout << "Using references to swap contents:\n";
swapr(wallet1, wallet2); // pass variables
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;

cout << "Using pointers to swap contents again:\n";
swapp(&wallet1, &wallet2); // pass addresses of variables
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;

cout << "Trying to use passing by value:\n";
swapv(wallet1, wallet2); // pass values of variables
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;

return 0;
}
void swapr(int & a, int & b) // use references
{
int temp;
temp = a; // use a, b for values of variables
a = b;
b = temp;
}
void swapp(int * p, int * q) // use pointers
{
int temp;
temp = *p; // use *p, *q for values of variables
*p = *q;
*q = temp;
}
void swapv(int a, int b) // try using values
{
int temp;
temp = a; // use a, b for values of variables
a = b;
b = temp;
}

The result is :

1
2
3
4
5
6
7
wallet1 = $300 wallet2 = $350
Using references to swap contents:
wallet1 = $350 wallet2 = $300
Using pointers to swap contents again:
wallet1 = $300 wallet2 = $350
Trying to use passing by value:
wallet1 = $300 wallet2 = $350

The reference and pointer methods both successfully swap the contents of the two wallets,whereas the passing by value method fails.

Operator Overloading

Operator overloading is a technique for giving object operations a prettier look.

Operator overloading is an example of C++ polymorphism.

1
2
3
4
5
6
int main()
{
Vector3f v (2,3,4);
Vector3f w (1,2,1);
std::cout << v + w << std::endl;
}

Friends

As you’ve seen,C++ controls access to the private portions of a class object.Usually,public class methods serve as the only access,but sometimes this restriction is too rigid to fit particular programming problems.In such cases,C++ provides another form of access:the friend.Friends come in three varieties:

  • Friend functions

  • Friend classes

  • Friend member functions

Why we need friends?

Often,overloading a binary operator (that is,an operator with two arguments) for a class generates a need for friends Multiplying a Time object by a real number provides just such a situation,so let’s study that case.

1
A = B * 2.75;

Remember,the left operand is the invoking object.

Translates to the following member function call:

1
A = B.operator*(2.75);

But what about the following statement?

1
A = 2.75 * B; // cannot correspond to a member function

Conceptually, 2.75 * B should be the same as B * 2.75 ,but the first expression cannot correspond to a member function because 2.75 is not a type Time object.Remember, the left operand is the invoking object,but 2.75 is not an object.So the compiler cannot replace the expression with a member function call.

One way around this difficulty is to tell everyone (and to remember yourself) that you can only write B * 2.75 but never write 2.75 * B .This is a server-friendly,client-beware solution,and that’s not what OOP is about.

However,there is another possibility—using a nonmember function.(Remember,most operators can be overloaded using either member or nonmember functions.) A nonmember function is not invoked by an object;instead,any values it uses,including objects,are explicit arguments.Thus,the compiler could match the expression

1
Time operator*(double m, const Time & t);

Using a nonmember function solves the problem of getting the operands in the desired order (first double and then Time ),but it raises a new problem:Nonmember functions can’t directly access private data in a class.Well,at least ordinary nonmember functions lack access.But there is a special category of nonmember functions,called friends,that can access private members of a class.

Creating friends

The first step toward creating a friend function is to place a prototype in the class declaration and prefix the declaration with the keyword friend . Like the code we showed in the beginning.

1
2
3
4
friend Vector3f operator*(const float& r, const Vector3f& v)
{
return Vector3f(v.x * r, v.y * r, v.z * r);
}

This prototype has two implications:

  • Although the operator*() function is declared in the class declaration,it is not a member function.So it isn’t invoked by using the membership operator.

  • Although the operator*() function is not a member function,it has the same access rights as a member function.

Are friends unfaithful to OOP?

At first glance, it might seem that friends violate the OOP principle of data hiding because the friend mechanism allows nonmember functions to access private data. However, that’s an overly narrow view. Instead, you should think of friend functions as part of an extended interface for a class. For example, from a conceptual point of view, multiplying a double by a Time value is pretty much the same as multiplying a Time value by a double . That the first requires a friend function whereas the second can be done with a member function is the result of C++ syntax, not of a deep conceptual difference. By using both a friend function and a class method, you can express either operation with the same user interface. Also keep in mind that only a class declaration can decide which functions are friends, so the class declaration still controls which functions access private data. In short, class methods and friends are simply two different mechanisms for expressing a class interface.

A common kind of friend: Overloading the << Operator

From the code in the begining,we see that :

1
2
3
4
friend std::ostream& operator<<(std::ostream& os, const Vector3f& v)
{
return os << v.x << ", " << v.y << ", " << v.z;
}

In its most basic incarnation,the << operator is one of C and C++’s bit manipulation operators;it shifts bits left in a value.But the ostream class overloads the operator,converting it into an output tool.Recall that cout is an ostream object and that it is smart enough to recognize all the basic C++ types.That’s because the ostream class declaration includes an overloaded operator<<() definition for each of the basic types.That is,one definition uses an int argument,one uses a double argument,and so on.So one way to teach cout to recognize a Vector3f object is to add a new function operator definition to the ostream class declaration.But it’s a dangerous idea to alter the iostream file and mess around with a standard interface. Instead,use the Vector3f class declaration to teach the Vector3f class how to use cout.


So this is the basic syntax, anyway, there’s still lots deeper knowledge there… if I have met much more advanced code, I will write this article again.


References:
[1]C++ Primer Plus.

#

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×