> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-fbfa8bee.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouse C++ 开发的编码风格指南

# C++ 风格指南

<div id="general-recommendations">
  ## 一般建议
</div>

以下是一些建议，而非硬性要求。
如果你在编辑代码，遵循现有代码的格式通常是合理的。
代码风格对于保持一致性很有必要。一致性能让代码更易于阅读，也更便于检索。
许多规则并没有严格的逻辑依据；它们更多是由约定俗成的实践决定的。

<div id="formatting">
  ## 格式化
</div>

**1.** 大部分格式化工作由 `clang-format` 自动完成。

**2.** 缩进为 4 个空格。请配置您的开发环境，使 Tab 键输入四个空格。

**3.** 左右花括号必须各占一行。

```cpp theme={null}
inline void readBoolText(bool & x, ReadBuffer & buf)
{
    char tmp = '0';
    readChar(tmp, buf);
    x = tmp != '0';
}
```

**4.** 如果整个函数体只有一条 `statement`，可以将其写在同一行。在花括号两侧添加空格 (行尾空格除外) 。

```cpp theme={null}
inline size_t mask() const                { return buf_size() - 1; }
inline size_t place(HashValue x) const    { return x & mask(); }
```

**5.** 对于函数，括号两侧不要加空格。

```cpp theme={null}
void reinsert(const Value & x)
```

```cpp theme={null}
memcpy(&buf[place_value], &x, sizeof(x));
```

**6.** 在 `if`、`for`、`while` 及其他表达式中，开括号前须加一个空格 (与函数调用不同) 。

```cpp theme={null}
for (size_t i = 0; i < rows; i += storage.index_granularity)
```

**7.** 在二元运算符 (`+`、`-`、`*`、`/`、`%` 等) 和三元运算符 `?:` 两侧添加空格。

```cpp theme={null}
UInt16 year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
UInt8 month = (s[5] - '0') * 10 + (s[6] - '0');
UInt8 day = (s[8] - '0') * 10 + (s[9] - '0');
```

**8.** 如果输入了换行符，请将运算符另起一行，并增加其前面的缩进。

```cpp theme={null}
if (elapsed_ns)
    message << " ("
        << rows_read_on_server * 1000000000 / elapsed_ns << " rows/s., "
        << bytes_read_on_server * 1000.0 / elapsed_ns << " MB/s.) ";
```

**9.** 如有需要，可以在行内使用空格来对齐内容。

```cpp theme={null}
dst.ClickLogID         = click.LogID;
dst.ClickEventID       = click.EventID;
dst.ClickGoodEvent     = click.GoodEvent;
```

**10.** 不要在运算符 `.`、`->` 两侧使用空格。

如有必要，operator 可以折行至下一行。此时，其前面的缩进量会相应增加。

**11.** 不要在一元运算符 (`--`、`++`、`*`、`&` 等) 与参数之间添加空格。

**12.** 逗号后加空格，逗号前不加空格。`for` 表达式中的分号同理。

**13.** 不要在 `[]` 运算符两侧使用空格。

**14.** 在 `template <...>` 表达式中，`template` 与 `<` 之间需加空格；`<` 之后和 `>` 之前不加空格。

```cpp theme={null}
template <typename TKey, typename TValue>
struct AggregatedStatElement
{}
```

**15.** 在类和结构体中，`public`、`private` 和 `protected` 与 `class/struct` 保持相同的缩进级别，其余代码向内缩进。

```cpp theme={null}
template <typename T>
class MultiVersion
{
public:
    /// 供使用的对象版本。shared_ptr 负责管理版本的生命周期。
    using Version = std::shared_ptr<const T>;
    ...
}
```

**16.** 如果整个文件使用相同的 `namespace`，且没有其他重要内容，则 `namespace` 内部无需缩进。

**17.** 如果 `if`、`for`、`while` 或其他表达式的块只包含单个 `statement`，则花括号是可选的。此时应将该 `statement` 单独置于一行。此规则同样适用于嵌套的 `if`、`for`、`while` ……

但如果内部 `statement` 包含花括号或 `else`，则外部块需要用花括号括起来。

```cpp theme={null}
/// 完成写入。
for (auto & stream : streams)
    stream.second->finalize();
```

**18.** 行尾不应有任何空格。

**19.** 源文件采用 UTF-8 编码。

**20.** 字符串字面量中可以使用非 ASCII 字符。

```cpp theme={null}
<< ", " << (timer.elapsed() / chunks_stats.hits) << " μsec/hit.";
```

**21.** 不要在同一行中写多个表达式。

**22.** 将函数内的代码按段分组，各段之间最多只留一个空行。

**23.** 函数、类等内容之间用一到两个空行分隔。

**24.** `A const` (与某个值相关) 必须写在类型名称之前。

```cpp theme={null}
//正确
const char * pos
const std::string & s
//错误
char const * pos
```

**25.** 声明指针或引用时，`*` 和 `&` 符号的左右两侧都应留空格。

```cpp theme={null}
//正确
const char * pos
//错误
const char* pos
const char *pos
```

**26.** 使用Template类型时，请用 `using` 关键字为其定义别名 (最简单的情况除外) 。

换句话说，Template参数只在 `using` 中指定，不会在代码里重复。

`using` 可以在局部声明，例如在函数内部。

```cpp theme={null}
//正确
using FileStreams = std::map<std::string, std::shared_ptr<Stream>>;
FileStreams streams;
//错误
std::map<std::string, std::shared_ptr<Stream>> streams;
```

**27.** 不要在同一条语句中声明多个不同类型的变量。

```cpp theme={null}
//错误
int x, *y;
```

**28.** 不要使用 C 风格转换。

```cpp theme={null}
//错误
std::cerr << (int)c <<; std::endl;
//正确
std::cerr << static_cast<int>(c) << std::endl;
```

**29.** 在类和结构体中，应在每个可见性作用域内分别对成员和函数进行分组。

**30.** 对于较小的类和结构体，没有必要将方法声明与实现分开。

任何类或结构体中的较小方法也是如此。

对于Template类和结构体，不要将方法声明与实现分开 (否则它们必须定义在同一个翻译单元中) 。

**31.** 代码行宽可以放宽到 140 个字符，而不是 80 个字符。

**32.** 如果不需要后缀形式，始终使用前缀自增/自减运算符。

```cpp theme={null}
for (Names::const_iterator it = column_names.begin(); it != column_names.end(); ++it)
```

<div id="comments">
  ## 注释
</div>

**1.** 务必为代码中所有不那么简单的部分添加注释。

这一点非常重要。编写注释的过程可能会帮助你意识到，这段代码其实没有必要，或者它的设计有问题。

```cpp theme={null}
/** Part of piece of memory, that can be used.
  * For example, if internal_buffer is 1MB, and there was only 10 bytes loaded to buffer from file for reading,
  * then working_buffer will have size of only 10 bytes
  * (working_buffer.end() will point to position right after those 10 bytes available for read).
  */
```

**2.** 注释可以根据需要写得详细一些。

**3.** 将注释放在其所说明的代码之前。少数情况下，也可以把注释放在同一行代码之后。

```cpp theme={null}
/** 解析并执行查询。
*/
void executeQuery(
    ReadBuffer & istr, /// 从何处读取查询（以及 INSERT 的数据，如适用）
    WriteBuffer & ostr, /// 将结果写入何处
    Context & context, /// 数据库、表、数据类型、引擎、函数、聚合函数……
    BlockInputStreamPtr & query_plan, /// 此处可记录查询执行方式的描述
    QueryProcessingStage::Enum stage = QueryProcessingStage::Complete /// SELECT 查询处理到哪个阶段为止
    )
```

**4.** 注释只能使用英文书写。

**5.** 如果你在编写库，请在主头文件中添加详细注释加以说明。

**6.** 不要添加不能提供额外信息的注释。尤其不要留下像下面这样的空注释：

```cpp theme={null}
/*
* Procedure Name:
* Original procedure name:
* Author:
* Date of creation:
* Dates of modification:
* Modification authors:
* Original file name:
* Purpose:
* Intent:
* Designation:
* Classes used:
* Constants:
* Local variables:
* Parameters:
* Date of creation:
* Purpose:
*/
```

该示例引自 [http://home.tamk.fi/\~jaalto/course/coding-style/doc/unmaintainable-code/。](http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/。)

**7.** 不要在每个文件开头写无意义的注释 (如 author、创建日期等) 。

**8.** 单行注释以三个斜杠开头：`///`，多行注释以 `/**` 开头。这些注释被视为“文档注释”。

注意：你可以使用 Doxygen 根据这些注释生成文档。但通常并不使用 Doxygen，因为在 IDE 中浏览代码更方便。

**9.** 多行注释的开头和结尾不能有空行 (多行注释的结束行除外) 。

**10.** 如果要注释掉代码，请使用普通注释，不要使用“文档注释”。

**11.** 提交前，删除那些被注释掉的代码片段。

**12.** 不要在注释或代码中使用脏话。

**13.** 不要使用大写字母。不要使用过多的标点符号。

```cpp theme={null}
/// WHAT THE FAIL???
```

**14.** 不要使用注释作为分隔符。

```cpp theme={null}
///******************************************************
```

**15.** 不要在注释中展开讨论。

```cpp theme={null}
/// Why did you do this stuff?
```

**16.** 无需在代码块末尾添加说明其用途的注释。

```cpp theme={null}
/// for
```

<div id="names">
  ## 名称
</div>

**1.** 变量和类成员的名称应使用带下划线的小写字母。

```cpp theme={null}
size_t max_block_size;
```

**2.** 对于函数 (方法) 名称，请使用首字母小写的驼峰命名法。

```cpp theme={null}
std::string getName() const override { return "Memory"; }
```

**3.** 类名 (struct) 应使用首字母大写的 CamelCase。interface 不使用 I 以外的前缀。

```cpp theme={null}
class StorageMemory : public IStorage
```

**4.** `using` 的命名方式与类名相同。

**5.** Template类型参数的命名：简单情况下，使用 `T`、`T`、`U`、`T1`、`T2`。

更复杂的情况下，要么遵循类名的命名规则，要么添加前缀 `T`。

```cpp theme={null}
template <typename TKey, typename TValue>
struct AggregatedStatElement
```

**6.** Template 常量参数的名称：要么遵循变量命名规则，要么在简单情况下使用 `N`。

```cpp theme={null}
template <bool without_www>
struct ExtractDomain
```

**7.** 对于抽象类 (接口) ，可以添加 `I` 前缀。

```cpp theme={null}
class IProcessor
```

**8.** 如果变量只在局部使用，可以使用短名称。

其他情况下，请使用能体现其含义的名称。

```cpp theme={null}
bool info_successfully_loaded = false;
```

**9.** `define` 和全局常量的名称使用带下划线的全大写形式。

```cpp theme={null}
#define MAX_SRC_TABLE_NAMES_TO_STORE 1000
```

**10.** 文件名应与其内容采用相同的命名风格。

如果文件只包含一个类，文件名应与类名一致 (CamelCase) 。

如果文件只包含一个函数，文件名应与函数名一致 (camelCase) 。

**11.** 如果名称中包含缩写，则：

* 对于变量名，缩写应使用小写字母，如 `mysql_connection` (不要写成 `mySQL_connection`) 。
* 对于类名和函数名，应保留缩写中的大写字母，如 `MySQLConnection` (不要写成 `MySqlConnection`) 。

**12.** 如果构造函数参数仅用于初始化类成员，其命名应与类成员相同，但末尾要加下划线。

```cpp theme={null}
FileQueueProcessor(
    const std::string & path_,
    const std::string & prefix_,
    std::shared_ptr<FileHandler> handler_)
    : path(path_),
    prefix(prefix_),
    handler(handler_),
    log(&Logger::get("FileQueueProcessor"))
{
}
```

如果该参数在构造函数体中未被使用，则可以省略下划线后缀。

**13.** 局部变量和类成员的命名没有区别 (无需前缀) 。

```cpp theme={null}
timer (not m_timer)
```

**14.** 对于 `enum` 中的常量，使用首字母大写的驼峰命名法。ALL\_CAPS 也是可以接受的。如果 `enum` 不是局部的，请使用 `enum class`。

```cpp theme={null}
enum class CompressionMethod
{
    QuickLZ = 0,
    LZ4     = 1,
};
```

**15.** 所有名称都必须使用英文。不允许将希伯来语单词音译。

不要使用 T\_PAAMAYIM\_NEKUDOTAYIM

**16.** 如果缩写广为人知 (也就是说，你可以很容易在 Wikipedia 或搜索引擎中查到其含义) ，则可以接受。

`AST`、`SQL`。

不要使用 `NVDH` (一些随意拼凑的字母)

如果截短后的形式已被广泛使用，也可以接受不完整的单词。

如果在注释中同时给出了全称，也可以使用缩写。

**17.** 包含 C++ 源代码的文件名必须使用 `.cpp` 扩展名。头文件必须使用 `.h` 扩展名。

<div id="how-to-write-code">
  ## 如何编写代码
</div>

**1.** 内存管理。

手动释放内存 (`delete`) 只能在库代码中使用。

在库代码中，`delete` 运算符只能在析构函数中使用。

在应用程序代码中，内存必须由其所属对象负责释放。

示例：

* 最简单的方法是将对象分配在栈上，或作为另一个类的成员。
* 对于大量小对象，请使用容器。
* 对于少量位于堆上的对象，如需自动释放，请使用 `shared_ptr/unique_ptr`。

**2.** 资源管理。

使用 `RAII`，并参见上文。

**3.** 错误处理。

使用异常。在大多数情况下，你只需要抛出异常，不需要捕获它 (因为有 `RAII`) 。

在离线数据处理应用程序中，通常可以不捕获异常。

在处理用户请求的服务器中，通常只需在连接处理程序的顶层捕获异常即可。

在线程函数中，你应当捕获并保存所有异常，以便在 `join` 之后在主线程中重新抛出。

```cpp theme={null}
/// 如果尚未开始任何计算，则同步计算第一个块
if (!started)
{
    calculate();
    started = true;
}
else /// 如果计算已在进行中，则等待结果
    pool.wait();

if (exception)
    exception->rethrow();
```

绝不要在未处理异常的情况下将其隐藏。也绝不要不加甄别地把所有异常都记入日志。

```cpp theme={null}
//不正确
catch (...) {}
```

如果必须忽略某些异常，也只应忽略特定的异常，其余异常都应重新抛出。

```cpp theme={null}
catch (const DB::Exception & e)
{
    if (e.code() == ErrorCodes::UNKNOWN_AGGREGATE_FUNCTION)
        return nullptr;
    else
        throw;
}
```

使用带有响应代码或 `errno` 的函数时，务必检查返回结果，并在出错时抛出异常。

```cpp theme={null}
if (0 != close(fd))
    throw ErrnoException(ErrorCodes::CANNOT_CLOSE_FILE, "Cannot close file {}", file_name);
```

你可以使用 assert 来检查代码中的不变量。

**4.** 异常类型。

应用程序代码中没有必要使用复杂的异常层次结构。异常信息应当让系统管理员能够理解。

**5.** 从析构函数中抛出异常。

不建议这样做，但这是允许的。

可采用以下做法：

* 创建一个函数 (`done()` 或 `finalize()`) ，提前完成所有可能导致异常的工作。如果该函数已经被调用，之后析构函数中就不应再出现异常。
* 过于复杂的任务 (例如通过网络发送消息) 可以放到单独的方法中，由类的使用者在对象销毁前调用。
* 如果析构函数中发生异常，最好将其记入日志，而不是悄悄忽略 (如果 logger 可用) 。
* 在简单的应用程序中，可以接受依赖 `std::terminate` (针对 C++11 中默认 `noexcept` 的情况) 来处理异常。

**6.** 匿名代码块。

你可以在单个函数内部创建单独的代码块，以将某些变量限制在局部作用域内，这样在退出该代码块时就会调用析构函数。

```cpp theme={null}
Block block = data.in->read();

{
    std::lock_guard<std::mutex> lock(mutex);
    data.ready = true;
    data.block = block;
}

ready_any.set();
```

**7.** 多线程。

在离线数据处理程序中：

* 尽量先把单个 CPU 核心上的性能做到最好。然后在有必要时再对代码进行并行化。

在服务器应用中：

* 使用线程池处理请求。到目前为止，我们还没有遇到任何需要 userspace 上下文切换的任务。

不要使用 fork 来做并行化。

**8.** 线程同步。

通常可以让不同线程使用不同的内存单元 (更好的是，不同的缓存行) ，从而避免使用任何线程同步机制 (`joinAll` 除外) 。

如果确实需要同步，大多数情况下，使用 `lock_guard` 保护的 mutex 就足够了。

其他情况下，使用系统同步原语。不要使用忙等待。

只有在最简单的情况下才应使用原子操作。

除非这正是你的主要专长领域，否则不要尝试实现无锁数据结构。

**9.** 指针与引用。

大多数情况下，优先使用引用。

**10.** `const`。

使用常量引用、指向常量的指针、`const_iterator` 和 `const` 方法。

将 `const` 视为默认选择，只有在必要时才使用非 `const`。

按值传递变量时，使用 `const` 通常没有意义。

**11.** unsigned。

必要时使用 `unsigned`。

**12.** 数值类型。

使用类型 `UInt8`、`UInt16`、`UInt32`、`UInt64`、`Int8`、`Int16`、`Int32` 和 `Int64`，以及 `size_t`、`ssize_t` 和 `ptrdiff_t`。

不要对数值使用这些类型：`signed/unsigned long`、`long long`、`short`、`signed/unsigned char`、`char`。

**13.** 传递参数。

如果复杂值后续需要被移动，则按值传递并使用 `std::move`；如果需要在循环中更新某个值，则按引用传递。

如果函数会接管在堆上创建的对象的所有权，应将参数类型设为 `shared_ptr` 或 `unique_ptr`。

**14.** 返回值。

大多数情况下，直接使用 `return` 即可。不要写 `return std::move(res)`。

如果函数在堆上分配对象并返回它，应使用 `shared_ptr` 或 `unique_ptr`。

在少数情况下 (例如在循环中更新某个值) ，你可能需要通过参数返回该值。这种情况下，该参数应为引用。

```cpp theme={null}
using AggregateFunctionPtr = std::shared_ptr<IAggregateFunction>;

/** 允许通过名称创建聚合函数。
  */
class AggregateFunctionFactory
{
public:
    AggregateFunctionFactory();
    AggregateFunctionPtr get(const String & name, const DataTypes & argument_types) const;
```

**15.** `namespace`。

没有必要为应用代码使用单独的 `namespace`。

小型库同样不需要这么做。

对于中大型库，应将所有内容都放在一个 `namespace` 中。

在库的 `.h` 文件中，可以使用 `namespace detail` 来隐藏应用代码不需要的实现细节。

在 `.cpp` 文件中，可以使用 `static` 或匿名 `namespace` 来隐藏符号。

此外，`namespace` 也可用于 `enum`，以防止相应名称进入外部 `namespace` (但更好的做法是使用 `enum class`) 。

**16.** 延迟初始化。

如果初始化需要参数，通常就不应该编写默认构造函数。

如果后续需要延迟初始化，可以添加一个默认构造函数来创建无效对象。或者，对于数量较少的对象，也可以使用 `shared_ptr/unique_ptr`。

```cpp theme={null}
Loader(DB::Connection * connection_, const std::string & query, size_t max_block_size_);

/// 用于延迟初始化
Loader() {}
```

**17.** 虚函数。

如果某个类不打算用于多态，就不需要将函数声明为 virtual。析构函数也一样。

**18.** 编码。

一律使用 UTF-8。使用 `std::string` 和 `char *`。不要使用 `std::wstring` 和 `wchar_t`。

**19.** 日志。

请参考代码中的各处示例。

提交前，删除所有无意义的日志、调试日志，以及其他任何类型的调试输出。

应避免在循环中记录日志，即使是 Trace 级别也不例外。

在任何日志级别下，日志都必须具有可读性。

在大多数情况下，日志只应在应用程序代码中使用。

日志消息必须用英文编写。

日志内容最好能让系统管理员看懂。

不要在日志中使用脏话。

日志应使用 UTF-8 编码。在极少数情况下，可以在日志中使用非 ASCII 字符。

**20.** 输入输出。

不要在对应用程序性能至关重要的内部循环中使用 `iostreams` (并且绝不要使用 `stringstream`) 。

请改用 `DB/IO` 库。

**21.** 日期和时间。

参见 `DateLUT` 库。

**22.** include。

始终使用 `#pragma once`，不要使用 include guard。

**23.** using。

不要使用 `using namespace`。可以对具体项使用 `using`，但要将其局部限制在类或函数内部。

**24.** 除非必要，否则不要对函数使用 `trailing return type`。

```cpp theme={null}
auto f() -> void
```

**25.** 变量的声明和初始化。

```cpp theme={null}
//正确方式
std::string s = "Hello";
std::string s{"Hello"};

//错误方式
auto s = std::string{"Hello"};
```

**26.** 对于虚函数，在基类中使用 `virtual`，但在派生类中应使用 `override`，而不是 `virtual`。

<div id="unused-features-of-c">
  ## C++ 中未使用的功能特性
</div>

**1.** 不使用虚继承。

**2.** 现代 C++ 中那些提供了便捷语法糖的构造，例如

```cpp theme={null}
// 不使用语法糖的传统方式
template <typename G, typename = std::enable_if_t<std::is_same<G, F>::value, void>> // 通过 std::enable_if 实现 SFINAE，使用 ::value
std::pair<int, int> func(const E<G> & e) // 显式指定返回类型
{
    if (elements.count(e)) // .count() 成员检测
    {
        // ...
    }

    elements.erase(
        std::remove_if(
            elements.begin(), elements.end(),
            [&](const auto x){
                return x == 1;
            }),
        elements.end()); // remove-erase 惯用法

    return std::make_pair(1, 2); // 通过 make_pair() 创建 pair
}

// 使用语法糖（C++14/17/20）
template <typename G>
requires std::same_v<G, F> // 通过 C++20 概念实现 SFINAE，使用 C++14 Template别名
auto func(const E<G> & e) // auto 返回类型（C++14）
{
    if (elements.contains(e)) // C++20 .contains 成员检测
    {
        // ...
    }

    elements.erase_if(
        elements,
        [&](const auto x){
            return x == 1;
        }); // C++20 std::erase_if

    return {1, 2}; // 或：return std::pair(1, 2); // 通过初始化列表或值初始化创建 pair（C++17）
}
```

<div id="platform">
  ## 平台
</div>

**1.** 我们为特定平台编写代码。

但在其他条件相同的情况下，优先选择跨平台或可移植的代码。

**2.** 语言：C++20 (参见[可用的 C++20 特性列表](https://en.cppreference.com/w/cpp/compiler_support#C.2B.2B20_features)) 。

**3.** 编译器：`clang`。在撰写本文时 (2025 年 3 月) ，代码使用 >= 19 版本的 clang 编译。

使用标准库 (`libc++`) 。

**4.** 操作系统：Ubuntu Linux，不早于 Precise。

**5.** 代码面向 x86\_64 CPU 架构编写。

CPU 指令集采用我们各台服务器均支持的最低指令集。目前为 SSE 4.2。

**6.** 使用 `-Wall -Wextra -Werror -Weverything` 编译选项，但有少数例外。

**7.** 除了那些难以静态链接的库之外，其他所有库都使用静态链接 (参见 `ldd` 命令的输出) 。

**8.** 代码在 release 配置下进行开发和调试。

<div id="tools">
  ## 工具
</div>

**1.** KDevelop 是一个不错的 IDE。

**2.** 调试时，使用 `gdb`、`valgrind` (`memcheck`) 、`strace`、`-fsanitize=...` 或 `tcmalloc_minimal_debug`。

**3.** 做性能分析时，使用 `Linux Perf`、`valgrind` (`callgrind`) 或 `strace -cf`。

**4.** 源代码存放在 Git 中。

**5.** 构建使用 `CMake`。

**6.** 程序通过 `deb` 包发布。

**7.** 提交到 master 的代码不得导致构建失败。

不过，只有选定的修订版本才被认为是可用的。

**8.** 尽可能频繁地提交，即使代码还只完成了一部分。

为此请使用分支。

如果你在 `master` 分支中的代码还无法构建，请在 `push` 之前将其从构建中排除。你需要在几天内完成它或将其删除。

**9.** 对于较复杂的更改，请使用分支并将其发布到服务器上。

**10.** 未使用的代码会从代码仓库中移除。

<div id="libraries">
  ## 库
</div>

**1.** 使用 C++20 标准库 (允许使用实验性扩展) ，以及 `boost` 和 `Poco` 框架。

**2.** 不允许使用操作系统软件包中的库，也不允许使用预装库。所有库都应以源代码的形式放在 `contrib` 目录中，并随 ClickHouse 一起构建。详见[添加新的第三方库指南](/zh/resources/develop-contribute/contribute/contrib#adding-and-maintaining-third-party-libraries)。

**3.** 始终优先选择已经在使用的库。

<div id="general-recommendations">
  ## 一般建议
</div>

**1.** 尽量少写代码。

**2.** 优先尝试最简单的解决方案。

**3.** 在弄清楚代码将如何工作以及内部循环如何运作之前，不要动手写代码。

**4.** 在最简单的情况下，优先使用 `using`，而不是类或结构体。

**5.** 如果可以，尽量不要编写拷贝构造函数、赋值运算符、析构函数 (如果类中至少包含一个虚函数，则虚析构函数除外) 、移动构造函数或移动赋值运算符。换句话说，编译器生成的这些函数应当能够正常工作。你也可以使用 `default`。

**6.** 鼓励简化代码。在可能的情况下，尽量减少代码量。

<div id="additional-recommendations">
  ## 其他建议
</div>

**1.** 对 `stddef.h` 中的类型显式加上 `std::`

不推荐这样做。换句话说，我们建议写 `size_t`，而不是 `std::size_t`，因为前者更简洁。

当然，加上 `std::` 也是可以接受的。

**2.** 对标准 C 库中的函数显式加上 `std::`

不推荐这样做。换句话说，应该写 `memcpy`，而不是 `std::memcpy`。

原因是还存在类似的非标准函数，例如 `memmem`。我们确实偶尔会用到这些函数，而它们并不存在于 `namespace std` 中。

如果你到处都写 `std::memcpy` 而不是 `memcpy`，那么不带 `std::` 的 `memmem` 看起来就会很奇怪。

不过，如果你更喜欢，也还是可以使用 `std::`。

**3.** 当标准 C++ 库中提供了相同函数时，使用 C 中的函数。

如果这样做效率更高，也是可以接受的。

例如，复制大块内存时，使用 `memcpy` 而不是 `std::copy`。

**4.** 多行函数参数。

以下任一种换行风格都可以：

```cpp theme={null}
function(
  T1 x1,
  T2 x2)
```

```cpp theme={null}
function(
  size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
```

```cpp theme={null}
function(size_t left, size_t right,
  const & RangesInDataParts ranges,
  size_t limit)
```

```cpp theme={null}
function(size_t left, size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
```

```cpp theme={null}
function(
      size_t left,
      size_t right,
      const & RangesInDataParts ranges,
      size_t limit)
```
