4.5 KiB
Notes on the C-to-C++ Conversion
-
libtransmission was written in C for fifteen years, so eliminating all Cisms is nearly impossible. Modernization patches are welcomed but it won't all happen overnight.
tr_strdup()
andconstexpr
wil exist side-by-side in the codebase for the forseeable future. -
It's so tempting to refactor all the things! Please keep modernization patches reasonably focused so that they will be easy to review.
-
Prefer
std::
tools over bespoke ones. For example, usestd::vector
instead of tr_ptrArray. Redundant bespoke code should be removed. -
Consider ripple effects before adding C++ into public headers. Will it break C code that #includes that header? If you think it might, consult with downstream projects to see if this is a problem for them.
Checklist for modernization of a module
NOTE:
The version inlibtransmission/CMakeLists.txt
is C++17.
See https://github.com/AnthonyCalandra/modern-cpp-features
This can be done in multiple smaller passes:
- Satisfy clang-tidy.
libtransmission/.clang-tidy
's list of rules should eventually be expanded to a list similar to the one inqt/.clang-tidy
. - Group member functions and their parent structs
- Use
namespace libtransmission
- Split large modules into smaller groups of files in a
libtransmission/<name>
subdirectories, with own sub-namespace.
- Use
- Enums replaced with new
enum class
syntax. Numeric#define
constants replaced with C++const
/constexpr
. - Memory - promote init and free functions to C++ ctors and dtors, and ensure it is only managed with new/delete
- Owned memory - promote simple pointer fields owning their data to smart pointers (unique_ptr, shared_ptr, vector, string)
Detailed Steps
-
Satisfy clang-tidy warnings
- Change C includes to C++ wraps, example: <string.h> becomes and update calls to standard library to
use
std::
namespace prefix. This clearly delineates the border between std library and transmission. Headers must be sorted alphabetically. - Headers which are used conditionally based on some
#ifdef
in the code, should also have same#ifdef
in the include section. - Revisit type warnings,
int
vsunsigned int
. Sizes and counts should usesize_t
type where this does not break external API declarations. Ideally change that too.
- Change C includes to C++ wraps, example: <string.h> becomes and update calls to standard library to
use
-
Move and group code together.
- Move member functions into structs. To minimize code churn, create function forward declarations inside structs,
and give
struct_name::
prefixes to the functions.
becomes:typedef struct { int field; } foo; int foo_blep(struct foo *f) { return f->field; }
struct foo { int field; void blep(); }; int foo::blep() { return this->field; }
- For functions taking
const
pointer, addconst
after the function prototype:int blep() const
like so. - For structs used by other modules, struct definitions should relocate to internal
*-common.h
header files. - Split large files into sub-modules sharing own separate sub-namespace and sitting in a subdirectory
under
libtransmission/
. - Some externally invoked functions must either not move OR have
extern "C"
adapter functions.
- Move member functions into structs. To minimize code churn, create function forward declarations inside structs,
and give
-
Enums promoted to
enum class
and given a type:enum { A, B, C };
becomes
enum: int { A, B, C }; // unscoped, use A, B, C enum MyEnum: int { A, B, C }; // unscoped, use A, B, C // OR wrap into a scope - enum struct MyEnum: int { A, B, C }; // scoped, use MyEnum::A enum class MyEnum: int { A, B, C }; // scoped, use MyEnum::A
this will make all values of enum to have that numeric type.
-
Numeric/bool
#define
constants should be replaced with C++const
/constexpr
. -
Memory management:
- Prefer constructors and destructors vs manual construction and destruction. But when doing so must ensure that the
struct is never constructed using C
malloc
/free
, but must use C++new
/delete
. - Avoid using std::memset on a new struct. It is allowed in C, and C++ struct but will destroy virtual table on a C++ class. Use field initializers in C++.
- Prefer constructors and destructors vs manual construction and destruction. But when doing so must ensure that the
struct is never constructed using C
-
Owned memory:
- If destructor deletes something, it means it was owned. Promote that field to owning type (vector, unique_ptr, shared_ptr or string).