Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated "Class declaration layout" style guideline #9

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 110 additions & 21 deletions docs/DeveloperResources/SquidCodingGuidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,41 +154,130 @@ extern void ReportUsage(ostream &); // global function CamelCased
## Class declaration layout

```c++
class Foo : public Bar
/// A non-normative illustration of selected layout rules.
/// While usually required, Doxygen member documentation is not shown here.
class Foo
{
CBDATA_* or MEMPROXY_* special macro.

public:
all public typedef

all constructors and operators
Foo destructor (if any)
using A = Bar::B;

/* Bar API */
all methods overloaded from FooBar parent class
Foo(const char *, size_t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to also highlight how single-argument constructor should
normally be marked explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but not right here, IMHO, because a proper treatment of that rule of thumb requires more text/explanation (e.g., we do not want folks to start adding explicit constructors where no constructor is needed at all). We do not even spell out that rule of thumb anywhere (yet). This simple class illustration is meant to focus/depict an overall class layout, without function-specific distractions/complications.

Foo(Foo &&) = delete;

all public static methods
all public member methods
void print(std::ostream &) const;

all public static variables
all public member variables
const InstanceId id;

protected:
all protected static methods
all protected member methods
uint64_t currentMemoryUsage() const;

all protected static variables
all protected member variables
uint64_t maximumMemoryUsage() const;

private:
all private static methods
all private member methods
friend class Bar;

static Storage &Cache();

SBuf buffer;
};
```

### Class section order

Class "sections" order (by member access specifiers): public, protected,
private. Each section, if present, declared once. Omit sections that would be
empty.

Rationale: List most commonly reused, most important things fist. In this
context, those are public class interfaces. The private section is the least
important implementation detail as far as the notion of a C++ class is
concerned.

### Class member order

Within each section, the recommended member order is defined below.

* friendship declarations
* type and aliases declarations; nested classes/structs/unions
* static member functions
* constructors and assignment operators
* destructors (just one until C++20)
* other non-static member functions except overrides
* overrides (see "Overridden virtual methods" subsection below)
* static data members (most are banned in new code by the "No new globals" rule)
* non-static data members

Rationale: Group similar things together to facilitate searching and highlight
differences. List things most likely to be reused (by other class members) and
most important/influential things higher.

### Overridden virtual methods

Overrides (including `final` declarations) are a special case where we do not
expect member descriptions but do expect a reference to the corresponding API
as sketched below. Overrides are grouped by the (direct or indirect) class
parent that _introduced_ the corresponding API method(s) (i.e. the parent
class that declared the virtual method but could _not_ use an override keyword
in that declaration). That class should describe the virtual method it
introduced.

```C++
/// Overrides example.
class Derived: public Base
{
public:
~Derived() override;

/* Foo::Grandfather API */
void sync() override;
void close() override;

/* Base API */
void print(std::ostream &) const override;

protected:
/* Foo::Grandfather API */
void maintain() override;
void sync() override;
};
```

Rationale: Provide API context and facilitate searching for member
descriptions without chasing overrides through parents.

Destructors should be declared with `override` specifier, but are _not_
grouped with other overrides.

all private static variables
all private member variables
### CBDATA- and MEMPROXY-related declarations

Squid has some legacy code that forces CBDATA- and MEMPROXY-related
declarations to be hoisted to the very top of the class, into the "unnamed"
section. Such hoisting is an exception to the above rules. Eventually, we will
stop hoisting, but we should continue doing that for consistency sake until we
get rid of the offending macros.

```C++
class Uri
{
MEMPROXY_CLASS(Uri);

public:
Uri();
};
```

### Caveats

The above rules are not meant to force authors to include any access
specifiers or members that the code does not actually need (except the
"private" specifier should be mentioned explicitly in class declarations that
have only private members -- do not rely on the class default access specifier
being "private").

Like any style rules, these rules are not comprehensive. If your use case is
not explicitly covered, then look around for similar Squid code and try to be
consistent and/or reasonable.

## Member naming

Pick one of the applicable styles described below and stick to it. For
Expand Down