Making C++ Code Beautiful - Gregory and McNellis - CppCon 2014
Making C++ Code Beautiful - Gregory and McNellis - CppCon 2014
Beautiful
JA MES MCN E L LIS K AT E G R EG ORY
M I CROS OF T V I S UAL C+ + G R EGORY CON S U LT ING L I M I TED
@ JA MESMCNELL IS @ G R EGCONS
C++ gets praise…
Powerful
Fast
Flexible
Language for smart people
…but C++ also gets criticism
Ugly
Complicated
Hard to read
Language for smart people
This is not C++
void f(char const* const file_name) {
void* buffer1 = malloc(1024 * 1024);
if (buffer1 != nullptr) {
FILE* file = fopen(file_name, "r");
if (file != nullptr) {
// ...Read data into the buffer, or whatever...
fclose(file);
}
free(buffer1);
}
}
The Basics (from Monday)
Compile at a high warning level
Stop writing C and try to port existing C code to C++
Avoid #ifdefs wherever possible; when they are necessary, keep them simple
Use RAII everywhere, even in the absence of exceptions
Keep functions linear and don’t write arrow code
Const-qualify everything (or, as much as possible )
Don’t use C casts—eliminate as many casts as possible and use C++ casts where necessary
Agenda
Macros are ugly
Walls of code are ugly
Lambdas are beautiful
Invisible code is beautiful
Removing effort is beautiful
Other people’s code is beautiful
Comments are ugly
Macros are ugly
Object-Like Macros
Often used for values
No encapsulation
No type (literal text)
void f()
{
unsigned orange = 0xff9900;
}
warning C4091: '' : ignored on left of 'unsigned int' when no variable is declared
error C2143: syntax error : missing ';' before 'constant'
error C2106: '=' : left operand must be l-value
#define red 0
#define orange 1
#define yellow 2
#define green 3
#define blue 4
#define purple 5
#define hot_pink 6
void f()
{
unsigned 2 = 0xff00ff;
}
warning C4091: '' : ignored on left of 'unsigned int' when no variable is declared
error C2143: syntax error : missing ';' before 'constant'
error C2106: '=' : left operand must be l-value
#define RED 0
#define ORANGE 1
#define YELLOW 2
#define GREEN 3
#define BLUE 4
#define PURPLE 5
#define HOT_PINK 6
error C2664: 'void g(color_type)' : cannot convert argument 1 from 'int' to 'color_type'
enum color_type
{
red, orange, yellow, green, blue, purple, hot_pink
};
void f()
{
int x = red; // Ugh
int x = red + orange; // Double ugh
}
enum color_type
{
red, orange, yellow, green, blue, purple, hot_pink
};
enum traffic_light_state
{
red, yellow, green
};
void make_string_lowercase(char* s)
{
while (make_char_lowercase(*s++))
;
}
#define make_char_lowercase(c) \
((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
void make_string_lowercase(char* s)
{
while (((*s++) = (((*s++) >= 'A') && ((*s++) <= 'Z'))
? ((*s++) - 'A' + 'a') : (*s++)))
;
}
// Old, ugly macro implementation:
#define make_char_lowercase(c) \
((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
return c;
}
Replace function-like macros with functions
Functions are…
◦ Easier to read and maintain
◦ Easier to write (you aren’t restricted to a single expression)
◦ Easier to debug through
◦ Behave better according to programmer expectations
[](){}
…but they are so useful!
auto vglambda = [](auto printer) {
return [=](auto&& ... ts) {
printer(std::forward<decltype(ts)>(ts)...);
return [=]() {
printer(ts ...);
};
};
};
std::vector<int> const v = { 1, 2, 3, 4, 5 };
switch (resolution_scope.table())
{
case table_id::module:
target_scope = &module.database();
break;
case table_id::module_ref:
target_scope = &resolve_module_ref(resolution_scope.as<module_ref_token>());
break;
case table_id::assembly_ref:
target_scope = is_windows_runtime_assembly_ref(assembly_ref_scope)
? &resolve_namespace(usable_namespace)
: &resolve_assembly_ref(assembly_ref_scope);
break;
default:
assert_unreachable();
}
database const* target_scope(nullptr);
switch (resolution_scope.table())
{
case table_id::module:
target_scope = &module.database();
break;
case table_id::module_ref:
target_scope = &resolve_module_ref(resolution_scope.as<module_ref_token>());
break;
case table_id::assembly_ref:
target_scope = is_windows_runtime_assembly_ref(assembly_ref_scope)
? &resolve_namespace(usable_namespace)
: &resolve_assembly_ref(assembly_ref_scope);
break;
default:
assert_unreachable();
}
database const* target_scope(nullptr);
switch (resolution_scope.table())
{
case table_id::module:
target_scope = &module.database();
break;
case table_id::module_ref:
target_scope = &resolve_module_ref(resolution_scope.as<module_ref_token>());
break;
case table_id::assembly_ref:
target_scope = is_windows_runtime_assembly_ref(assembly_ref_scope)
? &resolve_namespace(usable_namespace)
: &resolve_assembly_ref(assembly_ref_scope);
break;
default:
assert_unreachable();
}
database const& target_scope([&]() -> database const&
{
switch (resolution_scope.table())
{
case table_id::module:
return module.database();
case table_id::module_ref:
return resolve_module_ref(resolution_scope.as<module_ref_token>());
case table_id::assembly_ref:
return is_windows_runtime_assembly_ref(assembly_ref_scope)
? resolve_namespace(usable_namespace)
: resolve_assembly_ref(assembly_ref_scope);
default:
assert_unreachable();
}
}());
Invisible code is beautiful
Invisible code
What happens when flow of control reaches this line of code:
}
Sometimes, a lot
Look at your cleanup code
How long are your catch blocks?
… longer than the try?
Do your catch blocks have goto statements?
… more than 3?
What is your code trying to tell you?
ReConnect:
if (iReconnectCnt > 3)
{
goto exit_thread_loop;
}
do
{
while( TRUE )
{
if ( iRunState == RS_RUN )
break;
if ( iRunState == RS_STOPPING )
goto exit_thread_loop;
WaitForSingleObject(hChangeEvent, INFINITE);
}
try
{
OpenConnection(outfile, &connectInfo, pInfo, &txnRec, &pTxn );
}
catch(CBaseErr *e)
{
if ( (e->ErrorType() == ERR_TYPE_SOCKET) &&
((e->ErrorNum() == WSAECONNRESET) ||
(e->ErrorNum() == WSAECONNABORTED) ||
(e->ErrorNum() == ERR_CONNECTION_CLOSED)) )
{
CloseHTMLSocket(&connectInfo.socket);
iReconnectCnt++;
goto ReConnect;
}
goto exit_thread_loop;
}
catch(...)
{
goto exit_thread_loop;
}
exit_thread_loop:
if (bNeedToReleaseSemaphore)
{
ReleaseSemaphore( hTxnConcurrency, 1, NULL );
bNeedToReleaseSemaphore = FALSE;
}
Unexpected benefits
Consistent cleanup
Encapsulation
Important code becomes visible
Removing effort is
beautiful
Removing effort
Code that is hard to write is hard to read
Also to maintain, test, and fix
Code that states your intent clearly is easier for everyone
Code that cannot have certain mistakes is easier for everyone
Range-based for
Algorithms
auto--sometimes
std::vector<int> const v = { 1, 2, 3, 4, 5 };
for (n : v)
{
std::cout << n << '\n';
}
std::vector<int> const v = { 1, 2, 3, 4, 5 };
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="other">The source object</param>
basic_istream(const basic_istream &other);
/// <summary>
/// Assignment operator
/// </summary>
/// <param name="other">The source object</param>
/// <returns>A reference to the object that contains the result of the assignment.</returns>
basic_istream & operator =(const basic_istream &other);
struct basic_istream
{
basic_istream();
basic_istream(basic_istream const& other);
basic_istream& operator=(basic_istream const& other);
};
enum class day_of_week
{
sunday, // Sunday
monday, // Monday
tuesday, // Tuesday
humpday, // Humpday
thursday, // Thursday
friday, // Friday
saturday // Saturday
};
enum class day_of_week
{
sunday, // The first day of the week (value: 1)
monday, // The second day of the week (value: 2)
tuesday, // The third day of the week (value: 3)
humpday, // The fourth day of the week (value: 4)
thursday, // The fifth day of the week (value: 5)
friday, // The sixth day of the week (value: 6)
saturday // The seventh day of the week (value: 7)
};
enum class day_of_week
{
sunday,
monday,
tuesday,
humpday,
thursday,
friday,
saturday
};
int _read_nolock(
int fh,
void *inputbuf,
unsigned cnt
)
{
int bytes_read; /* number of bytes read */
char *buffer; /* buffer to read to */
int os_read; /* bytes read on OS call */
char *p, *q; /* pointers into buffer */
wchar_t *pu, *qu; /* wchar_t pointers into buffer for UTF16 */
char peekchr; /* peek-ahead character */
wchar_t wpeekchr; /* peek-ahead wchar_t */
__int64 filepos; /* file position after seek */
ULONG dosretval; /* o.s. return value */
char tmode; /* textmode - ANSI/UTF-8/UTF-16 */
BOOL fromConsole = 0; /* true when reading from console */
void *buf; /* buffer to read to */
int retval = -2; /* return value */
// ...
int _read_nolock(
int fh,
void *inputbuf,
unsigned cnt
)
{
int bytes_read; /* number of bytes read */
char *buffer; /* buffer to read to */
int os_read; /* bytes read on OS call */
char *p, *q; /* pointers into buffer */
wchar_t *pu, *qu; /* wchar_t pointers into buffer for UTF16 */
char peekchr; /* peek-ahead character */
wchar_t wpeekchr; /* peek-ahead wchar_t */
__int64 filepos; /* file position after seek */
ULONG dosretval; /* o.s. return value */
char tmode; /* textmode - ANSI/UTF-8/UTF-16 */
BOOL fromConsole = 0; /* true when reading from console */
void *buf; /* buffer to read to */
int retval = -2; /* return value */
// ...
int _read_nolock(
int fh,
void *inputbuf,
unsigned cnt
)
{
int bytes_read; /* number of bytes read */
char *buffer; /* buffer to read to */
int os_read; /* bytes read on OS call */
char *p, *q; /* pointers into buffer */
wchar_t *pu, *qu; /* wchar_t pointers into buffer for UTF16 */
char peekchr; /* peek-ahead character */
wchar_t wpeekchr; /* peek-ahead wchar_t */
__int64 filepos; /* file position after seek */
ULONG dosretval; /* o.s. return value */
char tmode; /* textmode - ANSI/UTF-8/UTF-16 */
BOOL fromConsole = 0; /* true when reading from console */
void *buf; /* buffer to read to */
int retval = -2; /* return value */
// ...
int _read_nolock(
int fh,
void *inputbuf,
unsigned cnt
)
{
int bytes_read; /* number of bytes read */
char *buffer; /* buffer to read to */
int os_read; /* bytes read on OS call */
char *p, *q; /* pointers into the buffer */
wchar_t *pu, *qu; /* wchar_t pointers into the buffer for UTF16 */
char peekchr; /* peek-ahead character */
wchar_t wpeekchr; /* peek-ahead wchar_t */
__int64 filepos; /* file position after seek */
ULONG dosretval; /* o.s. return value */
char tmode; /* textmode - ANSI/UTF-8/UTF-16 */
BOOL fromConsole = 0; /* true when reading from console */
void *buf; /* buffer to read to */
int retval = -2; /* return value */
// ...
/* lock the file */
_lock_fh(fh);
int main()
{
std::vector<int> const v = get_data_from_input_file();
int const largest_value = find_largest_value(v);
std::cout << "largest value: " << largest_value << '\n';
}
int find_largest_value(std::vector<int> const& v)
{
int largest_value = INT_MIN;
for (auto const i : v)
{
if (i > largest_value)
largest_value = i;
}
return largest_value;
}
int main()
{
std::vector<int> const v = get_data_from_input_file();
int const largest_value = *std::max_element(begin(v), end(v));
std::cout << "largest value: " << largest_value << '\n';
}
/*
int find_largest_value(std::vector<int> const& v)
{
int largest_value = INT_MIN;
for (auto const i : v)
{
if (i > largest_value)
largest_value = i;
}
return largest_value;
}
*/
int main()
{
std::vector<int> const v = get_data_from_input_file();
int const largest_value = *std::max_element(begin(v), end(v));
std::cout << "largest value: " << largest_value << '\n';
}
#if 0
int find_largest_value(std::vector<int> const& v)
{
int largest_value = INT_MIN;
for (auto const i : v)
{
if (i > largest_value)
largest_value = i;
}
return largest_value;
}
#endif
int main()
{
std::vector<int> const v = get_data_from_input_file();
int const largest_value = *std::max_element(begin(v), end(v));
std::cout << "largest value: " << largest_value << '\n';
}
int main()
{
std::vector<int> const v = get_data_from_input_file();
Comments that record date, time, author, and/or reason for change at the location of the
change
◦ Source control. And, ugh.