C++ Style Guide
While styling your code is not the most exciting part of software development, writing in a clean, consistent, and easy-to-read style sets you (and us, the instructors) up for success. Benefits of well-formatted code include the following:
-
Improved readability
-
Enhanced collaboration
-
Easier debugging
-
Better maintainability
-
Professionalism
-
Reduction in headaches and eye strain
While C++ style guidelines are unfortunately not standardized between professional organizations, we ask that you adhere to ours for the purposes of grading and our ability to help you debug your code if you ever find yourself stuck. We would, however, like to emphasize that these are guidelines, not rules. While we ask that you follow our guidelines to the best of your ability, we acknowledge that our guidelines are not perfect. If and when you do break them, leave a comment above the offending block, describing the breach and why it occurred. For example:
switch (static_cast<Image>(image_type)) {
case PNG:
std::cout << "PNG\n";
return portable network graphics;
case JPEG:
std::cout << "JPEG\n";
return "joint photographic experts group";
case WEBP:
std::cout << "WEBP\n";
return "web picture";
default:
// While the default case is unreachable, it resolves a GCC/Clang
// warning saying that "not all control paths return a value"
return "not an image format";
}
We will be using industry-standard tools to analyze your code, perhaps including clang-tidy, and cppcheck. We recommend installing these utilities. Additionally, we will be compiling your code with the -Wall, -Wpedantic, -Wextra, -std=c++20 flags, so it's recommended that you compile with those flags as well. Your grade is affected by how many warnings you have in your files, so be sure to resolve them when they appear.
Indentation, Braces, and Whitespace
Indentation & Braces
Always be consistent with your indentation, as the control flow of your program will be much easier to follow. We don't want to see submissions that look like this:
/* Gross! I can't follow this easily, and it makes me grumpy. Don't do this if you want a good grade */
std::vector<int>& n_squared_sort(std::vector<int>& vec) {
for (size_t i = 0; i < vec.size() - 1; ++i) {
bool is_swapped = false;
for (size_t j = 0; j < vec.size() - i - 1; ++j)
{
if (vec[j] > vec[j + 1]) {
std::swap(vec[j], vec[j + 1]);
is_swapped = true;
} else {
std::cout << "no swap\n"; }
}
if (!is_swapped)
{
break;
}
}
return vec; }
Instead, adhere to the following guidelines:
Indentation
We prefer 4 spaces for each indentation level as this is what Python will use later in the semester, but 2 spaces is also acceptable. Never mix tabs and spaces, as this will surely come back to haunt you once we get to the Python leg of the course. Additionally, some editors will display tabs as 4 spaces, others as 2, others as 6. It's best to avoid tabs whenever possible. Most editors can be configured to insert space characters whenever the TAB key is pressed.
Brace style
Use the One True Brace Style for your code. In short, all braces should reside on the same line as their declaration and constructs such as else and catch should start on the same line as the end of the related if and try:
One True Brace Style
// Function's first brace on the same line as its signature
std::vector<int>& nSquaredSort(std::vector<int>& vec) {
// Brace on same line as loop
for (size_t i = 0; i < vec.size() - 1; ++i) {
bool is_swapped = false;
for (size_t j = 0; j < vec.size() - i - 1; ++j) {
// Same line brace
if (vec[j] > vec[j + 1]) {
std::swap(vec[j], vec[j + 1]);
is_swapped = true;
// Same line brace and "cuddled" to the if statement
} else {
std::cout << "no swap\n";
}
}
// Braces required even for one-liners
if (!is_swapped) {
break;
}
}
return vec;
}
You may also use the Allman style if you prefer. It is similar to the One True Brace style, but opening braces are placed on the following line, at the same level of indentation as the declaration:
Allman Style
// Brace follows declaration
std::vector<int>& nSquaredSort(std::vector<int>& vec)
{
// Brace on next line
for (size_t i = 0; i < vec.size() - 1; ++i)
{
bool is_swapped = false;
for (size_t j = 0; j < vec.size() - i - 1; ++j)
{
if (vec[j] > vec[j + 1])
{
std::swap(vec[j], vec[j + 1]);
is_swapped = true;
}
// else is not cuddled to the if
else
{
std::cout << "no swap\n";
}
}
// Braces required even for one-liners
if (!is_swapped)
{
break;
}
}
return vec;
}
Any line that is above 100 columns should be broken up into more lines. Indent the overflow to line up with the first line of text:
// Too long, max width is column 144
std::chrono::duration<double> result = getElapsedTime(std::chrono::high_resolution_clock clock, std::chrono::duration<double> paused_duration);
// Better, max width is column 96
std::chrono::duration<double> result = getElapsedTime(std::chrono::high_resolution_clock clock,
std::chrono::duration<double> paused_duration);
Whitespace and blank lines
In general, there should be whitespace on either side of any arithmetic, bitwise, or logical operator. There should also be whitespace after loop, conditional, and namespace declarations as well as function arguments. There should not be whitespace after a function name or cast.
Place blank lines between declarations and implementations of functions. You may also place blank lines between steps of a function if you wish.
Don't submit code that looks like this if you care about your grade:
/* Don't make me read this, I will become upset */
int doWork (int first,int second,int third) {
int result=second[0];
for(size_t j=0;j<second.size()-1;++j) {
if(op_combination[j]=='+') {
result+=second[j+1];
}else if(op_combination[j]=='*') {
result*=second[j+1];
}else if(op_combination[j]=='|') {
result=std::stoi (std::to_string (result)+std::to_string (second[j+1])+first);
}
}
}
int main (){
int result=doWork (1,2,3);
return 0;
}
Do this instead:
// No space after function name. Spaces between function parameters and spaces between operators.
int doWork(int first, int second, int third) {
int result = second[0];
// Whitespace after the for loop declaration is not necessary, but acceptable
for (size_t j = 0; j < second.size() - 1; ++j) {
if (op_combination[j] == '+') {
result += second[j + 1];
} else if (op_combination[j] == '*') {
result *= second[j + 1];
} else if (op_combination[j] == '|') {
result = std::stoi(std::to_string(result) + std::to_string(second[j + 1]) + first);
}
}
}
// Blank line after function implementation
int main() {
int result = doWork(1 ,2, 3);
return 0;
}
For preprocessor directives, separate them from your main code with a blank line. Separate groups of directives with a blank line as well. Order your directives by the following order, with includes alphabetized:
- include guards (
#ifndef,#pragma once) - STL includes
- 3rd party library includes
- Includes from your project
#pragma once
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
#include <SFML/Graphics.hpp>
#include "DuckPond.hpp"
#include "Goose.hpp"
class PublicPark {
// Rest of the code...
}
Naming and Declarations
A famous quote in computer science reads:
"There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors."
Coming up with good names for functions, classes, enums, and files is difficult. The most important part is being consistent. Generally speaking, your names should be meaningful and their type easily inferred. Do not abbreviate, use single letter variables, include type information in the name, or declare more than one variable per line. If there are several keywords in the declaration, prefer this order:
[[maybe_unused]] [[nodiscard]] virtual const/constexpr volatile static inline auto/unsigned long long int* const function() const noexcept override;
-
For variables, use nouns in lowersnake_case or lowerCamelCase. It should answer the question "What is this?" If it is a boolean, prefix the name with the words "is," "has," or similar. Do not use
autounless you're sure of what you're getting back (see the "_Things to never do section for why). You may also use structured bindings, if you wish. Useconstas much as possible; if your variable isn't supposed to change, then guarantee it by declaring itconstso that you don't make a mistake down the line.- A count of rubber ducks:
unsigned num_ducks = 0; - A player's score in a Player struct:
double score = 1.0; - The name of a book:
const std::string bookTitle = "The Count of Monte Cristo"; - A boolean checking if a Player is a winner:
bool is_winner; - A variable using type inference:
auto board = boardCopy.get2DVector(); - Structured binding, assuming
getDimensions()returns astd::pair<int, int>:const auto [x, y] = getDimensions()
- A count of rubber ducks:
-
For functions, use verbs combined with a noun in lowerCamelCase. The name should answer the question "What does this do?" Prefix the function with [[maybe_unused]] if it isn't being used by your code, and [[nodiscard]] if the return value should always be used. Avoid using trailing return types. If the function makes no changes to any parameters, declare the parameters
const. If the function makes no changes to member variables, declare the functionconst.- A function that combines two vectors:
[[nodiscard]] std::vector<int> combine(std::vector<int> first_vector, std::vector<int> second_vector); - A function that renders a main menu window on the screen:
void renderMainMenu(sf::RenderWindow& window); - A function that pauses the game:
void pause(Game* game) - A function that calculates the average of a vector:
[[nodiscard]] double getAverage(const std::vector<double>& scores) const; - Do not declare functions with trailing return types:
auto function(int param) -> std::string;
- A function that combines two vectors:
-
Pointers and references should be declared with the "*" or "&" characters next to the variable's type or next to the variable's name. Do not put the character with whitespace on both sides.
- Left aligned:
const char* arr = "array" - Right aligned:
const char *arr = "array"
- Left aligned:
-
Classes, structs, and aliased types should be in PascalCase. Private and protected class members should follow the above guidelines, but should suffix the variable name with a trailing underscore.
- A class of a duck pond:
class DuckPond - A struct of a Linked-list node:
struct ListNode - An alias with the
usingkeyword:using Clock = std::chrono::steady_clock - Pointer to a Player held in a Game class: `Player* redplayer;
- A class of a duck pond:
-
Exceptions:
-
For a loop variable, it's fine to have a one-letter name:
for (size_t i = 0; i < arr.size(); ++i) -
When overloading assignment operators, it's common to name the parameters "lhs" and "rhs" for left-hand-side and right-hand-side respectively.
-
Instead of having magic numbers in your code, replace them with a named variable in SCREAMING_SNAKE_CASE that is declared
constexpr. For example:bool checkScoreValidity(const std::vector<int>& scores) {
for (const int score : scores) {
// Why 1600?
if (score > 1600) {
return false;
}
return true;
}
}bool checkScoreValidity(const std::vector<int>& scores) {
constexpr int MAX_SAT_SCORE = 1600;
for (const int score : scores) {
// Much clearer
if (score > MAX_SAT_SCORE) {
return false;
}
return true;
}
}enummembers should be snake_case.
enum class HexColors : int {
gator_blue = 0x0021A5, gator_orange = 0xFA4616, gator_green = 0x01723E
}- If in everyday speech, an abbreviation or acronym is more commonly said than the full name, use that abbreviation in a variable, class, or function name. Treat the abbreviation as a word (i.e. don't capitalize individual letters in it)
Image united_states_of_america_flagshould be shortened toImage usa_flagstd::vector<std::vector<unsigned char>> parseTruevisionGraphicsAdapter(const Image& image)can be shortened tovoid parseTga(const Image& image)class CommandLineInterfaceParsershould be shortened toclass CLIParser
-
Classes and Structs
C++'s first name was "C with Classes" when it was in its infancy. Object oriented programming is perhaps the biggest paradigm in C++, and so getting classes right is essential.