Protected members, function overloading and new operators for pvector
protected members (example N4) and function overloading (example N5).pvector you wrote last time.pvector — a constructor with an initializer_list, +=, -=, scalar product, norm(), vector times and divided by a scalar, *=, /=, == and the cross product ^.Along the way we go back to some ideas of Lab #1 that the new methods use all the time: const methods, the pointer this and the object *this, returning by reference, const references, temporary objects.
class vecder: public myvec inherits all the members of myvec and adds show(). But show() cannot read v[i]: v is private in myvec, so it has to go through the public methods get() and length().= and + written as methods of vecdder; v3 = v1 + v2 is v3.operator=(v1.operator+(v2)).Today:
private and public: protected;Who can use a member, according to where it is declared:
| member declared | methods of the class | methods of a derived class | outside (e.g. main) |
|---|---|---|---|
private |
yes | no | no |
protected |
yes | yes | no |
public |
yes | yes | yes |
class the default is private (in a struct it is public).protected: hidden from the users of the class, visible to who extends it.class dercl: public basecl (public inheritance): the inherited members keep their level. The private ones are in the derived object too, but it cannot touch them.private (or protected if derived classes need them), methods public. With v protected in myvec, vecder::show() (N2) could use v[i] directly.protected.hppexamples/N4_protected_members/protected.hpp
basecl has one member per level: a, fa() private; b, fb() protected; c, fc() public.dercl::g() calls fc() and fb() and reads b; with return a; it stops: error: 'int basecl::a' is private within this context.g() has no access specifier in dercl: it is private, the default of a class.protected.cppexamples/N4_protected_members/protected.cpp
main sees only the public members of dercl: v.c, v.fc() (inherited from basecl). Try the other ones in place of v.fc();:Several functions (or methods) with the same name and different parameters (number or types). The compiler chooses, at compile time, from the types of the arguments:
int f(int) and double f(int) together give error: ambiguating new declaration of 'double f(int)'.abs, labs, fabs); in C++ one name is enough, std::abs, overloaded for int, long, double, …+ exists for int, for double and, since Lab #1, for pvector. Today we overload the constructor of pvector and operator* (two versions).class vecder { public: void show(void) const; ... }; // in the base class (N2)
class vecddder: public vecdder // N5, derives from N3
{
public:
void show(std::string txt) const; // a new show(...)
};
vecddder v1;
v1.show("v3:"); // ok
v1.show(); // error: no matching function for call to 'vecddder::show()'show in vecddder, finds it, and does not look further up.using vecder::show; in the derived class brings the versions of the base back into play: then all the show are overloads of each other.vecder::show(): the scope operator :: calls the version of a given class.func_overloading.hppexamples/N5_func_overloading/func_overloading.hpp
#include "../N3_overloading/overloading.hpp"
class vecddder: public vecdder
{
public:
// For overloading, we need to explicitly say that we want to use show
// of base class method as public method of derived class too
using vecder::show;
// overloading of show method which now has an argument (a string)
void show(std::string txt) const
{
std::cout << txt;
vecder::show();
}vecddder (three d) derives from vecdder (N3), which derives from vecder (N2), where show() is defined: three versions of show, with no argument (from vecder, thanks to the using of line 7), with a std::string, with an int.show(int) builds the string "v1=": std::to_string(a) turns a number into a std::string, and + concatenates strings. Then it calls the string version.func_overloading.cppexamples/N5_func_overloading/func_overloading.cpp
(0,1,2)
v3:(0,1,2)
v1=(0,1,2)
"v3:" (a C string, converted to std::string), 1 (an int).using vecder::show;): v1.show(); does not compile any more, error: no matching function for call to 'vecddder::show()'.show()project/pvector.hpp
pvector() sets all the NT components to 0: every pvector starts in a known state. Uncomment the std::cout lines to see when the vectors are created and destroyed.v is a fixed-size array, it disappears with the object.show() is const (it only reads) and loops over NT.sum() and operator+project/pvector.hpp
VT, filled component by component, returned by value.VT.v[i] is legal: inside a method of pvector the private members of any pvector are accessible.operator+ repeats the loop of sum(); it could just as well be return sum(V2);.operator-project/pvector.hpp
-.v[i] is a shorthand for (*this).v[i], the components of the object on the left of -; V2.v[i] are those of the argument.get(), set() and the testproject/pvector.hpp
get() is const, set() is not: it changes the object.return v[i]=val;: as in C, an assignment is an expression, and its value is the value assigned. set() assigns and returns val in one line.main of Lab #1 (pvector.cpp) the output is the one expected:C=(0,0,0)
A=(1,2,3)
B=(0.5,-1,2)
A(1)=2
A+B=(1.5,1,5)
A-B=(0.5,3,1)
A.sum(B)=(1.5,1,5)
E=(1,2,3)
PC=(0,0,0)
pvectorDownload again project_classes.zip (course page; unzip it elsewhere, not over your own file): the new project/pvector.hpp contains the methods of Lab #1, implemented (Part 2), and the skeleton of today’s methods. The TODO list is updated:
// ...
// [X] get( ) (get i-th element)
// [X] set( , ) (set i-th element)
// [*] constructor with overloading->initializer_list
// [*] overloading of += and -= operators with vectors
// [*] overloading of * operator for scalar product (vector times vector)
// [*] norm() norm of vector, implemented by using overloaded operator "*"
// [*] overloading of * operator for "vector times scalar" product
// [*] overloading of / operator for "vector divided by scalar" product
// [*] overloading of *= and /= (with scalars)
// [*] overloading of operator == (check whether two vectors are equal)
// [*] overloading of ^ operator for cross product
// [ ] overloading of (...) operator () to use vector as C arrays, e.g. v(0)=1
// ...The skeleton gives comments, signatures and empty bodies: fill them. Until then it compiles with 10 warnings no return statement in function returning non-void, as in Lab #1. The next slides explain the ideas behind the signatures; then a main to test everything.
project/pvector.hpp: assign() and operator= do the same thing, only the name changes.
The compiler translates every operator into a call of a method of the left operand (*this inside the method), with the right operand as argument:
The long forms compile too: try A.assign(B);, A.operator=(C);, D = A.operator+(B);.
operator+, v[i] are the components of A (*this), V2.v[i] those of B.const at the end of a method protects the left operand: A + B, A*B, A == B are const methods, A = B and D += B are not. The right operand is protected by the const in const pvector& V2.return (*this);, type pvector&): this is why A = B = C works. The others return a new object.A*B and A*2.0 are two different methods of A, operator*(const pvector&) and operator*(ntype): function overloading (Part 1), chosen from the type of the right operand.2.0*A would be 2.0.operator*(A): double is not a class and has no methods, so it does not compile. In a next lab: a function outside the class (friend).A ^ B into A.operator^(B) (see the cross product).this and *thisthis, a pointer to the object it is called on. Writing v[i] inside a method means this->v[i], that is (*this).v[i]: the components of that object.*this is the object itself (* applied to a pointer, as in C). You need it when a method must use or return the whole object:
return (*this); in operator= and today in +=, -=, *=, /=;(*this)*(*this) in norm(): the scalar product of the object with itself.V2.v[i] are the components of the argument, v[i] those of *this.const methodsconst method this points to a const pvector: the method can only read the object. Assigning v[i] inside it is a compile error.const methods can be called on a const object or through a const reference. A norm() without const, used on a const pvector& V: error: passing 'const pvector' as 'this' argument discards qualifiers.const every method that does not change the object.today, const |
today, not const |
|---|---|
A*B, norm(), A*s, A/s, A==B, A^B: they compute a new value |
+=, -=, *=, /=: they change the object on the left |
const referenceconst pvector&: no copy of the vector (&), and the method promises not to change it (const). Always use it for vector arguments.ntype s) is passed by value: copying a double costs nothing.const reference accepts temporary objects too: in A*(B+C) the argument is B+C, a vector with no name that lives only in that expression. With a non-const pvector& it does not compile: error: cannot bind non-const lvalue reference of type 'pvector&' to an rvalue of type 'pvector'.| returns | what | when |
|---|---|---|
pvector (by value) |
a new vector, copied to the caller | +, -, A*s, A/s, ^: the result is a local VT |
pvector& (by reference) |
the object itself, no copy | =, +=, -=, *=, /=: return (*this); |
ntype, bool |
a number | A*B, norm(), == |
*this exists before and after the call. A local VT dies at the end of the method: a reference to it points to a dead object (Lab #1).+= returns pvector&, like operator=, so (D += B) *= 2.0; changes D twice. If it returned pvector (by value), *= 2.0 would act on a copy of D, and D would only get += B.+ vs +=The operators that are not assignments (+, -, *, /, ^) must not change their operands: A + B changes neither A nor B, so the result has to go into a new object, a temporary:
r += v*dt; (one temporary, v*dt) rather than r = r + v*dt; (two).operator+=: the modelNo local vector: the result is written directly into the object.
operator-= is the same, with -=.
operator*= and operator/= take a number:
initializer_listGoal: give the components when the vector is created, with braces:
Overloading of the constructor: a class can have several constructors, with different parameters. pvector D; calls pvector(), the braces the new one (skeleton):
std::initializer_list<ntype>: the list of values written between braces, all of type ntype (header <initializer_list>, already included). It cannot be changed; L.size() is the number of values.
pvector() must stay: once a class has a constructor, the compiler does not generate the void one any more, and without it pvector D; gives error: no matching function for call to 'pvector::pvector()'.
initializer_listThe values of L are read with a range-based for (C++11):
Read it as “for each x in L”. Keep your own counter i for the component to fill.
The list may have a number of values different from NT: decide what to do. For example missing components set to 0, extra values ignored:
Never write beyond v[NT-1].
norm()ntype), not a vector. Accumulate the sum in a local ntype initialized to 0.norm(), \(|\mathbf{A}| = \sqrt{\mathbf{A}\cdot\mathbf{A}}\), must use the operator just written: (*this)*(*this) is the scalar product of the object with itself. sqrt comes from <cmath>.const.operator* of the class: A*B (a pvector on the right) is the scalar product, A*2.0 the vector times scalar. A*2 works too: the int 2 is converted to ntype.2.0*A (a double on the left) does not compile: error: no match for 'operator*' (operand types are 'double' and 'pvector').0 is not checked: the components become inf or nan.operator==bool: true only if all the components are equal. You can return as soon as you find two different ones. std::cout prints a bool as 1 or 0.E = {0.1, 0.2, 0.3}, (E*3.0)/3.0 == E is false, because 0.1*3.0 is 0.30000000000000004 (std::cout with its default 6 digits shows 0.3, which hides it).== is good for vectors copied from each other, not for the results of computations. Those are compared within a tolerance: (A-B).norm() < 1e-10.^\[\mathbf{A}\times\mathbf{B} = (A_1B_2 - A_2B_1,\; A_2B_0 - A_0B_2,\; A_0B_1 - A_1B_0)\]
VT, by value), const.NT equal to 3: otherwise print a message and stop the program with exit(1) (from <cstdlib>, already included).(A^B)*A and (A^B)*B must be 0.^^ is the bitwise xor of C. Overloading changes what an operator does, not its precedence nor its number of operands: ^ comes after +, -, * and even ==.
Always use parentheses: (A ^ B) + C, D == (A ^ B). The same for <<: std::cout << A == B; is (std::cout << A) == B, an error; write std::cout << (A == B);.
mainA main that tests all the new methods (your pvector.cpp):
#include "./pvector.hpp"
int main(void)
{
pvector A = {1.0, 2.0, 3.0}; // initializer_list constructor
pvector B{0.5, -1.0, 2.0}; // the same, without =
pvector C = {4.0}; // missing components: 0
pvector D; // void constructor
A.show("A=");
B.show("B=");
C.show("C=");
D = A; D += B; D.show("D=A, D+=B: ");
D -= B; D.show("D-=B: ");
std::cout << "A*B=" << A*B << "\n";
std::cout << "|A|=" << A.norm() << "\n";
D = A*2.0; D.show("A*2=");
D = A/2.0; D.show("A/2=");
D = A; D *= 3.0; D.show("D=A, D*=3: ");
D /= 3.0; D.show("D/=3: ");
std::cout << "A==B: " << (A == B) << "\n";
std::cout << "A==D: " << (A == D) << "\n";
D = A ^ B; D.show("A^B=");
std::cout << "(A^B)*A=" << (A^B)*A << "\n";
return 0;
}A=(1,2,3)
B=(0.5,-1,2)
C=(4,0,0)
D=A, D+=B: (1.5,1,5)
D-=B: (1,2,3)
A*B=4.5
|A|=3.74166
A*2=(2,4,6)
A/2=(0.5,1,1.5)
D=A, D*=3: (3,6,9)
D/=3: (1,2,3)
A==B: 0
A==D: 1
A^B=(7,-0.5,-2)
(A^B)*A=0
(A^B)*A=0: the cross product is orthogonal to both vectors, a quick check of ^.std::cout << "A*B=" << A*B no parentheses are needed: * comes before <<.-Wall, and everything must work with ntype = float.+= returning pvector instead of pvector&: it compiles, D += B works, but (D += B) *= 2.0 changes a copy.const on operator*, norm(), ==, ^: they cannot be used on const objects nor on const pvector& arguments (... discards qualifiers).pvector& without const: temporaries like B+C are refused (cannot bind non-const lvalue reference ...).ntype sp; contains garbage, ntype sp=0;.pvector() when adding the initializer_list constructor: pvector D; no longer compiles.v[NT-1] with a list longer than NT.^: always parentheses.== on the results of computations: use a tolerance.Next lab: operator() to use a pvector as a C array (V(0)=1), “scalar times vector” with a friend function, rint(), element-wise products, << for output.