Notes for C/C++ Programming
Off by One
Error Prone
#define LEN 128
char buf[LEN] = {0};
// -1 for prevent missing NULL terminator
// since strncpy doesn't preserve it for us.
strncpy(buf, something, sizeof(buf) - 1);
// snprintf does preserve NULL terminator for us
// i.e. actual size for placing characters is sizeof(buf) - 1
// but it takes extra time to parsing format
snprintf(buf, sizeof(buf), something);
Bad things often happen when we mess with -1
or +1
to pointers point to continuous elements.
Absorber
#define LEN 128
char buf[LEN + 1] = {0};
// ^^^^ Always preserve 1 byte
strncpy(buf, somethin, sizeof(buf));
Constant Correctness (C/C++)
Bad
void do_something(char *dest, char *src);
struct MyList {
void do_something(std::string &name);
};
Reason: Will the name
be modified by the lookup
method? Will state of a MyList
instance be changed after calling the method?
Good
void do_something(char *dest, char const* src);
struct MyList {
void do_something(std::string const &name) const;
};
Be explicit!
For-Loop (C++)
C++98
vector<int> vec{1,2,3};
for(vector<int>::iterator i = vec.begin(); i != vec.end(); ++i) { /* ... */ }
Use Auto and Global Iterator Operating Functions
#include <iterator>
// ...
vector<int> vec{1,2,3};
for(auto i = begin(vec); i != end(vec); next(i)) { /* ... */}
Note Other than next
, we also have prev
.
Use Range-base For-loop
vector<int> vec{1, 2, 3}; // Uniform initializer
// i is a `copy` of an element in vec
for(auto i : vec) { /* .... */ }
// i is a `reference` to an element in vec
for(auto &i : vec) { /* .... */ }
Heap Allocation/Free (C )
Check NULL After Allocation
char *p = (char*) malloc(n);
if (!p) {
// error out
}
Initialize As Zero With calloc
struct S {
char s[4];
char const *p;
};
// Create and initialize with zeroed content
struct S *my_struct = (struct S*) calloc(4, sizeof(struct S));
// if my_struct is not NULL then following assertions hold
assert(my_struct[0].s[0] == 0);
assert(my_struct[0].p == 0);
Free NULL Doesn’t Hurt But Dereference It Does
void MyFree(void **pptr)
{
if(!ptr) return; // Undefined behavior per standard
free(*ptr); // Safe if *ptr == NULL per standard
}
GCC/Clang Extension __cleanup__
Attribute
NOT Supported by MSVC (It has try-final and __leave)
// Sometimes called poor man's destructor
#define SCOPED_FREE(Function) __attribute__((__cleanup__(Function)))
void free_char_p(char **ptr) { if(ptr) { free(*ptr); *ptr = NULL; } }
if(...) {
SCOPED_FREE(free_char_p) char *s = malloc(4);
// Call free(x) automatically when exiting scope
}
Sometimes its convenient to define new type for it.
#define local_string_t \
SCOPED_FREE(free_char_p) char
if(...) {
local_string_t *s = ...,
*y = ...;
}
Be careful when you need to assign different address to the pointer. See below.
local_string_t *s = malloc (5);
s = malloc(6); // previous allocated memory is leaked
Heap Management (C++)
WRONG
Surprisingly but I saw it in production code.
int *ptr = new int;
if (!ptr) {
// Never reach
}
// ...
delete ptr;
Use std::nothrow
int *ptr = new (std::nothrow) int;
if (!ptr) {
// If `new` failed, handle it here
}
// ...
delete ptr;
Use Try-Catch
int *ptr = std::nullptr;
try {
ptr = new int;
} catch (std::bad_alloc) {
// Handle allocation failure
}
delete ptr;
Use Smart Pointers
#include <memory>
// ...
std::unique_ptr<int> ptr;
try {
ptr = std::make_unique<int>(5);
} catch (std::bad_alloc) {
// Handle allocation failure
}
// Note here, we don't do `delete`.
Try to find out when to use std::shared_ptr
and std::unique_ptr
.
Wrap C Create/Release Interfaces (C++)
// C interfaces
Handle *create_handle();
void free_handle(Handle *ptr);
std::unique_ptr<Handle> hdl = make_unique<Handle>(create_handle(), &free_handle);
// the free_handle() will be called when hdl exits scope.
Containers (C++)
Feature | Candidate |
---|---|
C++ Compatible C-array | std::array<T> . |
General/Prototyping | std::vector<T> . |
Better for insertion | std::list<T> . |
Fast lookup | std::set<T> and std::map<T, U> . |
Even faster lookup | std::unordered_set<T> and std::unordered_map<T, U> . |
Algorithms & Lambda (C++)
Find
#include <algorithm>
#include <vector>
// ...
std::vector<int> vec{1,3,4};
// Find the first even number
auto iter = std::find_if(vec.begin(), vec.end(),
[](auto const &val){
return 0 == val % 2;
});
if (iter != vec.end()) {
// found
} else {
// not found
}
Sort
struct point
{
int x, y;
};
std::vector<point> points = {
{1, 2}, {3,4}, {1,5}, {2, 6}
};
// sort by point.x in descending order
std::sort(points.begin(), points.end(),
[](int const &x, int const &y){
return x.x > y.x;
});
Remove
std::vector<int> vec{1,3,4, 8, 9, 11, 14};
auto predictor = [](auto const &val){
return 0 == val % 2;
};
// Move all matched elements to tail portion of the vector.
auto new_end = std::remove_if(vec.begin(), vec.end(), predictor);
vec.erase(new_end, vec.end());
Error Handling (C/C++)
Hard to Read
if (ok_1) {
if(ok_2) {
if(ok_3) {
} else {
}
} else {
}
} else {
}
Less Nesting with goto
int ret = 0;
if(!ok_1) {
ret = E_INVALID;
goto ERROR_;
}
if (!ok_2) {
}
// you got the idea ...
ERROR_:
log_error(ret);
return ret;
Less Nesting with while
int ret=0;
while (1) {
if (!ok_1) {
ret = E_INVALID;
break;
}
if(!ok_2) {}
// ...
break;
}
Never Mix Exception and Error Code
NEVER
int never_do_so() {
int ret = 0;
throw std::runtime_error();
return ret;
}
Pick one of exception or error code, and convert all underneath to the selected one. e.g.
Convert to Error Code
int func() {
int ret = 0;
try {
this_may_throw_bad_alloc();
this_can_throw_out_of_range();
this_throw_custom_exception();
} catch(std::bad_alloc) {
ret = E_NOMEM;
} catch(std::out_of_range) {
ret = E_RANGE;
} catch(...) {
ret = E_UNKNOWN;
}
return ret;
}
and vice versa.
Handle bad_alloc
I’ve mentioned new
can throw, and it is used in C++ standard library. Unless specified by ISO standard, many methods of standard library can throw. e.g.
// Default constructor usually won't throw
std::string s;
std::vector<int> v;
try {
// Following expressions can throw
std::vector<int> v2{1, 2, 3};
std::vector<int> v_cp(v2);
v.push_back(123);
v = v2;
s += "abcd";
s.insert(0, "abcd");
} catch (bad_alloc){
}
// Move or deletion won't throw
v.clear();
v.swap(*v2);
s.pop_back();
v.assign({4, 5, 6});
Written with StackEdit.
留言
張貼留言