10B_CodingPractices (1)
10B_CodingPractices (1)
Coding is not just about “getting the job done”. This sort of mentality can
lead to code that is difficult to read or debug, inefficient, and prone to
error.
There is also one other nice “feature” of C++: the language is always
evolving. New features and syntax are always being added; while code
from ancient C++ versions will work on most modern compilers, many of
the added functionalities are worth studying and learning because they
can shorten or simplify otherwise complex procedures, while maintaining
readability.
The C++ Standard
In this course some excerpts will come from the C++ Standard, generally
quoted in italics and distinguished by the highly technical legalese.
The C++ Standard
Think of "the Standard" like "the law": it governs what compilers are
required to do and how code should behave.
Without knowing the rules of the language, programmers may make bad
assumptions and adopt poor coding practices that make code unsafe to
use.
Remark: just as not all people are law-abiding, some compilers do not
fully adhere to the Standard. But most do a pretty good job once a
feature has been around for a while.
Notes on Behaviour
"There are three floating-point types float, double, and long double.
[...] The set of values of the type float is a subset of the set of values
of the type double; the set of values of the type double is a subset of
the set of values of the type long double. The value representation of
floating-point types is implementation-defined."
String literals get a spot in memory but in the simple code below:
std::vector<int> v;
// a bunch of push_backs...
for(int i=0; i < v.size(); ++i) {/* stuff */} // unsafe: int might overflow
Review: size_t
A class and struct are both classes; when declared by class, the default
access level is private and when declared as struct, the default access
level is public.
The default access level remains until a public or private (or other)
access modifier is used.
Review: class and struct
In access restrictions of public vs private, the two classes are the same:
class X {
int a() const; // private by default
public:
int b() const; // public due to specifier
int c() const; // also public
private:
int d() const; // private due to specifier
};
struct X {
int b() const; // public by default
private:
int a() const; // private due to specifier
int d() const; // also private
public:
int c() const; // public due to specifier
};
Review: class and struct
Usually we use structs as "simple" classes: they only store data and lack
member functions or else they only have member functions but no data.
Mutator functions may change the class member variables and are not
declared as const.
class A {
public:
A(); // has a default constructor
int get_a() const { return a; }
void double_a() { a *= 2; }
private:
int a;
};
This content is protected and may not be shared, uploaded, or distributed.
PIC 10B, UCLA
Header files are often used to declare functions, variables, and provide
class interfaces.
CPP files are often used to define those functions, classes, and
variables, and make use of them.
Review: Header vs CPP Files
To guard against multiple definitions of a class, all header files should
begin with:
#ifndef _SOME_NAME_
#define _SOME_NAME_
#endif
#ifndef _CAT_
#define _CAT_
struct cat {
/**
makes the cat meow
/*
void meow() const;
};
/**
makes a cat talk
@param _cat the cat that will talk
*/
void talk(const cat& _cat);
#endif
Review: Header vs CPP Files
#include<iostream>
#include "Cat.h"
#include "Cat.h"
int main() {
cat c;
talk(c);
return 0;
}
Review: Usings and Namespaces I
inside of a header file because all CPP or H-files that include such a
header file will be forced to conform to the standard namespace.
Header.h
#ifndef _HEADER_
#define _HEADER_
#include<string>
using namespace std; // evil being done here
#endif
main.cpp
#include "Header.h"
class string { }; // a user’s string class...
int main() {
string s; // ERROR: ’string’ is ambiguous
return 0;
}
Review: Passing by Reference vs by Value
void foo(int, double); // the int and double are both copies
/* the string and vector are both copies and the string copy cannot be
modified in the function */
This can be costly if class objects are being copied. It is also true that
changing a copy does not change the original.
Review: Passing by Reference vs by Value
that the 7 is copied, string1 will not be modified, someset will not be
modified, and that j and string2 could be modified...
Variable Definition Placement
// Less clear
int y = 42;
// many lines of code not using y at all
// ...
std::cout << y; // first use of y
Review: std::endl vs '\n'
Besides '\n' being a char and std::endl being a function (or pointer to a
function), there is a fundamental difference between the two.
With std::endl, a new line character is printed and the output stream
buffer is flushed (ensuring everything is printed to the console). This
overrides some optimizations a compiler may use to make the code run
faster by only flushing at optimal times.
For faster code, unless flushing "right then and there" is really important
'\n' is preferred.
#include<iostream>
int i = 42; // global variable defined here
int main(){
int j = 0;
while(i < 100) { // woops, we meant to write j...
std::cout << j++;
}
return 0;
}
means:
I first, i is set to 9 (double implicitly made into int);
I then, j is created but has some random unassigned value;
I then isqrd is set to i*i (81);
I then, s is default constructed and set to "";
I then v is set to a std::vector<char> storing 100 *’s.
I After this, s is replaced by "PIC 10B"
I and j is overwritten to be 7.
Review: Constructor Initializer Lists
A more efficient implementation:
struct Bar {
Bar(int theInt, size_t theSize, const std::string& theString) : i(theInt),
j(7), isqrd(i*i), s(theString), v(theSize, '*') { }
int i, j, isqrd;
std::string s;
std::vector<char> v;
};
Remark 2: there are many versions of C++ out there. One of the biggest
"revolutions" took place with C++11. Then came C++14 and C++17.
C++20 is the current most up-to-date version, but C++23 is already being
planned.
Review: iterators, std::begin, std::end, etc.
Review: iterators, std::begin, std::end, etc.
one
3
four
// same as:
// std::vector<std::string>::const_iterator iter = std::cbegin(vec);
int i = 42;
auto& j = i; // same as: int& j = i;
const int k = 12;
auto& L = k; // same as: const int& L = k;
auto m = k; // same as: int m = k;
const auto n = k; // same as: const int n = k;
Subtleties apply when the && symbol is used with auto. More on this
much later.
Review: Range-Based For-Loop and ‘auto’ Keyword
Unless we add the & symbol, auto will not make a reference.
std::string s("hi");
auto t = s; // t is a copy of s
auto& u = s; // u is a reference to s
// past-end
auto end = std::end(words);
for( auto itr = std::begin(words) ; itr != end; ++itr) { // move through set
int x = 13;
std::cout << static_cast<double>(x)/2;
is preferable to
The reason is that the C-style cast used directly above includes other
types of casts:
reinterpret_cast,
const_cast, in addition to
static_cast.
The value nullptr is a pointer literal. When one needs a pointer that
points to null, it is the most appropriate choice.
std::srand(std::time(nullptr));
Although the end result of the lines of code below are the same, they are
not, strictly speaking, the same.
The former two are copy initializations and the latter is called direct
initialization.
In the absence of compiler optimization (which does often happen), direct
initialization is always more efficient than copy initialization. But some
data types prohibit copy initialization entirely in which case only direct
initialization can work.
Review: Direct vs Indirect Initialization
Remark: when a function accepts arguments by value or returns by
value, copy initialization is done.
std::string quuz() {
return "hi";
}
When bar is called, the local variables i and s are copy-initialized from
the input arguments.
The output std::string from quuz is copy initialized from the value in its
return statement.
Review: Functions and Classes with Default
Arguments
Before the body of a constructor is run, any member variables that are
not initialized in the consructor initializer list will be set by their default
values if any are provided:
struct PIC10B {
PIC10B() : funLevel(INT_MAX) { } // constructor
class S {
private:
std::string msg = "hello";
public:
// no copying done: just reference the value
const std::string& get_msg() const { return msg; }
};
// ...
S s;
std::cout <<s.get_msg(); // directly references member, no copy!
Review: Return Type Efficiency
std::string& not_good() {
std::string local("not good"); // being made inside this function!
// ...
std::string& danger = not_good();
Sometimes a variable can be set at compile time rather than run time.
/**
Function computes the factorial of its input
@param n the value to compute n! for
@return n factorial = n(n-1)...(2)(1) or 1 if n=0
*/
constexpr int factorial(int n) {
int nfact = 1;
// if n==0, nfact stays as 1
for(int i=1; i <= n; ++i) { // multiply 1 thru n
nfact *= i;
}
return nfact;
}
// to be continued ...
constexpr II
int main() {
constexpr int x = factorial(3); // done at compile time, 6
constexpr int y = factorial(x); // done at compile time, 720
A "literal type" is, for simplicity right now (some important cases are
blatantly ignored), a fundamental type like int, bool, double, etc.; or a
class that only has fundamental type member variables.
Often the two runtimes are nearly identical so we mostly do it for nicer
looking code.
Subtlety: Passing by Reference vs by Value II
struct Vector {
float x = 0, y = 0;
};
Consider a function add that makes a new Vector whose x and y values
are the corresponding sum of the x and y values of its inputs.
Vector a; // a==(0,0)
a.x = 3; // a==(3,0)
a.y = 4; // a==(3,4)
Vector b; // b==(0,0)
b.x = 10; // b==(10,0)
Vector c = add(a,b); // so c == (13,4)
Subtlety: Passing by Reference vs by Value III
The sum of the two vectors means creating a new vector whose x-value
is the sum of the two x values; likewise with the y-value.
Neither input is modified: a copy is made of the first argument and the
second is passed as a reference to const.
Subtlety: Passing by Reference vs by Value I
struct X {
X() { }
X(int,double) { }
};
Then we use X:
X x(); // ERROR: should have written "X x;" to default construct, but...
X x{}; // okay, default constructs x
X x2{3,7.6}; // same as: "X x(3,7.6);"
X foo() {
return { }; // same as "return X();" or "return X{};"
}
Braced Initialization
struct Y {
X x{4,11.1};
};
Braced Initialization
Warning: braced initialization can be a bit dangerous for data types that
also accept lists of values to store...
f :R→R
x 7→ x 2
This syntax can also be quite useful with templates (to come).
Remark: the trailing return syntax does not alter how we otherwise call a
function! The previous function f could still be called with f(3.14), for
example.
Aside: Error in using NULL in place of nullptr
time(NULL);