콘텐츠로 이동

C.concrete

C.concrete: Concrete types

Concrete type rule summary:

C.10: Prefer concrete types over class hierarchies

Reason

A concrete type is fundamentally simpler than a type in a class hierarchy: easier to design, easier to implement, easier to use, easier to reason about, smaller, and faster. You need a reason (use cases) for using a hierarchy.

Example

class Point1 {
    int x, y;
    // ... operations ...
    // ... no virtual functions ...
};

class Point2 {
    int x, y;
    // ... operations, some virtual ...
    virtual ~Point2();
};

void use()
{
    Point1 p11 {1, 2};   // make an object on the stack
    Point1 p12 {p11};    // a copy

    auto p21 = make_unique<Point2>(1, 2);   // make an object on the free store
    auto p22 = p21->clone();                // make a copy
    // ...
}

If a class is part of a hierarchy, we (in real code if not necessarily in small examples) must manipulate its objects through pointers or references. That implies more memory overhead, more allocations and deallocations, and more run-time overhead to perform the resulting indirections.

Note

Concrete types can be stack-allocated and be members of other classes.

Note

The use of indirection is fundamental for run-time polymorphic interfaces. The allocation/deallocation overhead is not (that's just the most common case). We can use a base class as the interface of a scoped object of a derived class. This is done where dynamic allocation is prohibited (e.g. hard-real-time) and to provide a stable interface to some kinds of plug-ins.

Enforcement

???

C.11: Make concrete types regular

Reason

Regular types are easier to understand and reason about than types that are not regular (irregularities requires extra effort to understand and use).

The C++ built-in types are regular, and so are standard-library classes such as string, vector, and map. Concrete classes without assignment and equality can be defined, but they are (and should be) rare.

Example

struct Bundle {
    string name;
    vector<Record> vr;
};

bool operator==(const Bundle& a, const Bundle& b)
{
    return a.name == b.name && a.vr == b.vr;
}

Bundle b1 { "my bundle", {r1, r2, r3}};
Bundle b2 = b1;
if (!(b1 == b2)) error("impossible!");
b2.name = "the other bundle";
if (b1 == b2) error("No!");

In particular, if a concrete type is copyable, prefer to also give it an equality comparison operator, and ensure that a = b implies a == b.

Note

For structs intended to be shared with C code, defining operator== may not be feasible.

Note

Handles for resources that cannot be cloned, e.g., a scoped_lock for a mutex, are concrete types but typically cannot be copied (instead, they can usually be moved), so they can't be regular; instead, they tend to be move-only.

Enforcement

???

C.12: Don't make data members const or references in a copyable or movable type

Reason

const and reference data members are not useful in a copyable or movable type, and make such types difficult to use by making them at least partly uncopyable/unmovable for subtle reasons.

Example; bad

class bad {
    const int i;    // bad
    string& s;      // bad
    // ...
};

The const and & data members make this class "only-sort-of-copyable" -- copy-constructible but not copy-assignable.

Note

If you need a member to point to something, use a pointer (raw or smart, and gsl::not_null if it should not be null) instead of a reference.

Enforcement

Flag a data member that is const, &, or && in a type that has any copy or move operation.