Comments are notes the compiler strips out before it generates code. C++ gives you two real syntaxes, // and /* ... */, plus a documentation convention on top of those that tools like Doxygen read. This lesson covers each form, the rules that cause problems (especially the no-nesting rule for block comments), and the habits that keep comments useful instead of letting them rot.
C++ has exactly two comment syntaxes built into the language: single-line and multi-line. Everything else, including doc comments, is built on top of these two.
The compiler removes every comment during translation, so they have zero runtime cost. The two syntaxes do the same job at different scales. A quick reference:
| Style | Syntax | Spans | Used For |
|---|---|---|---|
| Single-line | // note | One line | Short notes, trailing remarks on a code line |
| Multi-line | /* note */ | Many lines | Longer blocks, file headers, sometimes inline inside expressions |
| Doc-style | /// or /** */ | Either | API documentation read by tools like Doxygen |
The rest of the lesson walks through each style, then looks at the bigger question: when comments actually help and when they get in the way.
A single-line comment starts with // and runs to the end of the line. The newline closes it. Anything after // on that line is ignored by the compiler.
Two placements show up here. The first comment sits on its own line above the code it describes. The second is a trailing comment, tacked onto the end of a code statement. Both are fine, and both stop at the newline.
A few things to remember:
// does not extend across lines. Each new line needs its own // for a multi-line note in this style.//, and ignores the rest of the line.// was originally a C++ feature. C did not get // until C99, so older C code only used /* ... */. In modern C++ code, // is the more common choice.A multi-line comment, also called a block comment, starts with /* and continues until the matching */. The compiler treats everything between those markers as whitespace, including newlines.
A * lined up on every line of a block comment is common. It is purely cosmetic. The compiler only looks at the opening /* and the closing */. Both of these are equivalent:
Block comments can also appear inside an expression, which // cannot do. This is occasionally useful for inline notes about a single argument:
The /*taxRate=*/ block is a labeled-argument hint to readers, telling them what the 0.08 means. It does not change behavior, but it makes the call site easier to skim. Modern alternatives (named struct fields, designated initializers) are usually cleaner, but this pattern appears in older codebases.
One rule worth knowing: block comments do not nest. The compiler matches the first */ it finds against the opening /* and treats anything after that as code.
What is wrong with this code?
The compiler reads the first /* as opening a comment, then closes that comment at the first */, right after "Inner comment opens here." Everything after that is code, so "Outer was supposed to close here." becomes a syntax error, and the trailing */ is a stray token. g++ reports something like:
Fix: Do not nest. Use a single block, or comment line by line with //:
Most editors and IDEs map a keyboard shortcut to "toggle line comment", which prepends // to every selected line. That sidesteps the nesting problem entirely.
/* */The no-nesting rule creates a real problem: how do you temporarily disable a block of code that already contains a /* */ comment? Wrapping it in another /* */ will not work, because the inner */ closes the outer comment early.
There are two practical ways out.
Option 1: Use // on every line. This always works because line comments do not have an "opening" and "closing" pair to clash with anything inside.
Option 2: Use the preprocessor's #if 0 ... #endif. This is cleaner when the disabled block is large or already contains block comments. The preprocessor strips out everything between #if 0 and #endif before the compiler sees it, and block comments inside do not matter at all.
The #if 0 directive evaluates to false, so the preprocessor removes the guarded region entirely. The compiler never sees the disabled code, including any /* ... */ comments inside it. To re-enable the code later, change #if 0 to #if 1.
This is a sharp tool, not an everyday one. The Namespaces & Preprocessor section covers preprocessor directives in depth. Treat #if 0 ... #endif as the escape hatch when normal comment syntax cannot handle the job.
C++ does not have a built-in documentation generator the way some languages do. Instead, there is a convention: write comments in a special format above a function, class, or variable, and run an external tool that reads them and produces HTML or PDF docs. The de-facto standard tool is Doxygen, and the conventions it understands have become the unofficial C++ doc-comment style.
Doxygen accepts two comment shapes:
Both forms attach to whatever declaration comes right after them. A documented calculateShipping function:
The text inside the /** ... */ block does two jobs at once. To a human reading the source, it describes what calculateShipping does and what each parameter means. To Doxygen, the @ tags pull out structured information that becomes a function reference page in the generated HTML.
Common tags:
| Tag | Used On | Meaning |
|---|---|---|
@brief | Any element | One-line summary, shown in lists and indexes |
@param | Functions, methods | Describes one parameter (one tag per parameter) |
@return | Functions, methods | Describes the return value |
@note | Any element | Highlighted note or caveat |
@throws or @exception | Functions, methods | An exception the function may throw |
@see | Any element | Cross-reference to a related symbol |
@deprecated | Any element | Marks the element as discouraged, with a reason |
Installing Doxygen is not required to write doc comments. They are still useful as in-source documentation, and the format is consistent enough that anyone reading your code recognizes it. When the project later decides to publish API docs, the comments are already in the right shape.
This is a brief intro, not a Doxygen tutorial. The tool has dozens of tags, configuration options, and output formats. If your team uses it, the project will have a Doxyfile and conventions to follow.
The hardest part of writing comments is not the syntax, it is deciding what to write. The most useful rule: explain WHY, not WHAT. The code already tells the reader what it does. A comment should explain things the code cannot.
Compare these two versions of the same function:
The first version's comments restate the operators. Any reader can see subtotal * (1 + taxRate), so "multiply subtotal by 1 plus taxRate" is filler. The second version explains *why*: the rounding mode matches a business expectation. That information does not appear anywhere in the code.
Useful comments tend to cover:
Comments that hurt more than they help:
// increment count above count++.// ====== SECTION ======. Blank lines and good function names work better.Comments cost nothing at runtime, but they cost reader trust. A wrong comment is worse than no comment because readers waste time reconciling code with a description that drifted.
The function works, but the commented-out block at the top is dead weight. It clutters the file, drifts further out of sync every time the surrounding code changes, and offers nothing a reader can act on. The "in case product wants to roll back" comfort is illusory: nobody is going to come back six months later, find this block, and trust it as a reliable rollback path.
Fix: Delete the dead code. Version control remembers every line ever committed, so the history is already preserved in git log and git blame:
If you need a record of the old behavior alongside the new, write a short comment that explains what changed and link to the commit or ticket. That is far more useful than a copy of the code itself.
TODO and FIXME markers are useful when they carry enough information for someone other than the author to act on them. A bare // TODO: fix this tells the next reader nothing. A good marker includes who is responsible (or at least who wrote it), what needs to happen, and ideally a ticket link.
That marker survives the original author leaving the team, because the ticket carries the context. A // FIXME follows the same pattern but signals something actively broken, not just a future improvement.
A handful of guidelines that keep these markers useful:
The best comment is often no comment at all, replaced by a name that makes the code obvious on its own. If you are writing a comment to clarify what a variable or function does, first ask whether a better name would do the same job:
remainingStock(currentStock, unitsSold) reads like English. There is nothing left for a comment to clarify. Save comments for things names cannot carry: the why, the constraints, the surprising rules.
Small functions help too. A 200-line function needs comments to break it into chunks. A 15-line function with a good name does not need section headers because the name already says what it does.
C++ does not have a single official rule about whether to use // or /* ... */ for ordinary comments. Both are valid. The choice is mostly about code review and consistency within a codebase, not technical correctness. Most modern C++ codebases prefer // for everyday comments because:
*/)./* ... */ still shows up for file headers, long block comments, and inline-in-expression notes. Doc comments use /// or /** ... */ depending on team convention.
The right answer is whatever your project already does. If you join a team and every file uses /* ... */ block headers, write block headers. If they use //, use //. Consistency matters more than the choice itself.
A few habits keep comments from rotting:
/// or /** */. Private helpers rarely do.10 quizzes