QT编码规范
工作中需要用到QT项目,依据QT的一些官方文档做一些整理。
- Qt 编码风格 Qt Coding Style
- 代码规范 Coding Conventions
- API设计原则 API Design Principles
- 优秀API的六大特性 Six Characteristics of Good APIs
- 静态多态性 Static Polymorphism
- 基于属性的API Property-Based APIs
- QProperty
- C++特性 C++ Specifics
- API语义与文档说明 API Semantics and Documentation
- 命名的艺术 The Art of Naming
- 避免常见陷阱 Avoiding Common Traps
- 案例学习 Case Studies
- 参考
Qt 编码风格 Qt Coding Style
本节翻译自:https://wiki.qt.io/Qt_Coding_Style
缩进 Indentation
- 缩进为4个空格(4 spaces are used for indentation)
- 使用空格作为缩进,不适用tab(Spaces, not tabs!)
变量声明 Declaring variables
- 声明每一个变量都要用独立的一行(Declare each variable on a separate line)
- 避免短的或无意义的命名(Avoid short or meaningless names (e.g. "a", "rbarr", "nughdeget"))
- 单个字符的变量名只适用于非常明白该变量意思的场景下(比如临时变量)(Single character variable names are only okay for counters and temporaries, where the purpose of the variable is obvious)
- 当一个变量被用到时再声明它(Wait when declaring a variable until it is needed)
// Wrong
int a, b;
char *c, *d;
// Correct
int height;
int width;
char *nameOfThis;
char *nameOfThat;
- 变量和函数以小写字母开头。之后的部分每个单词以大写字母开头(Variables and functions start with a lower-case letter. Each consecutive word in a variable's name starts with an upper-case letter)
- 避免缩写(Avoid abbreviations)
// Wrong
short Cntr;
char ITEM_DELIM = ' ';
// Correct
short counter;
char itemDelimiter = ' ';
- 类名以大写字母开头,公开类以Q开头,紧跟大写字母;公用函数以q开头。(此为Qt内部规范,我们可不遵守)(Classes always start with an upper-case letter. Public classes start with a 'Q' (QRgb) followed by an upper case letter. Public functions most often start with a 'q' (qRgb).)
- 首字母缩写词采用驼峰命名法,如QXmlStreamReader,而不是QXMLStreamReader(即只有第一个字母大写)(Acronyms are camel-cased (e.g. QXmlStreamReader, not QXMLStreamReader).)
空白 Whitespace
- 用空行在适当的地方划分代码块(Use blank lines to group statements together where suited)
- 总是只用一个空行(Always use only one blank line)
- 在关键词和花括号之间总是只用一个空格符(Always use a single space after a keyword and before a curly brace:)
// Wrong
if(foo){
}
// Correct
if (foo) {
}
- 对于指针或引用,在类型名和
*或&之间用一个空格,但是在*或&和变量名之间不要有空格(For pointers or references, always use a single space between the type and '*' or '&', but no space between the '*' or '&' and the variable name:)
char *x;
const QString &myString;
const char * const y = "hello";
- 二元操作符的左右都要有空格(Surround binary operators with spaces)
- 逗号后面需要一个空格(Leave a space after each comma)
- cast类型装潢后无须空格(No space after a cast)
- 避免C风格的cast类型转换(Avoid C-style casts when possible)
// Wrong
char* blockOfMemory = (char* ) malloc(data.size());
// Correct
char *blockOfMemory = reinterpret_cast<char *>(malloc(data.size()));
- 不要在一行写多条语句(Do not put multiple statements on one line)
- 另起一行写控制流语句的正文(By extension, use a new line for the body of a control flow statement:)
// Wrong
if (foo) bar();
// Correct
if (foo)
bar();
括号 Braces
- 使用紧贴括号:左括号和语句的开头在同一行,如果右括号是紧跟在一个关键词之后的,则右括号和该关键词在同一行(Use attached braces: The opening brace goes on the same line as the start of the statement. If the closing brace is followed by another keyword, it goes into the same line as well:)
// Wrong
if (codec)
{
}
else
{
}
// Correct
if (codec) {
} else {
}
- 例外:函数的实现和类的声明中,左括号总是在一行的开头(Exception: Function implementations (but not lambdas) and class declarations always have the left brace on the start of a line:)
static void foo(int g)
{
qDebug("foo: %i", g);
}
class Moo
{
};
- 当条件语句的执行部分多于一句的时候才使用花括号(Use curly braces only when the body of a conditional statement contains more than one line:)
// Wrong
if (address.isEmpty()) {
return false;
}
for (int i = 0; i < 10; ++i) {
qDebug("%i", i);
}
// Correct
if (address.isEmpty())
return false;
for (int i = 0; i < 10; ++i)
qDebug("%i", i);
- 例外1:如果父语句占有多行,或经过多层封装,子语句要用到花括号(Exception 1: Use braces also if the parent statement covers several lines / wraps:)
// Correct
if (address.isEmpty() || !isValid()
|| !codec) {
return false;
}
- 例外2:对称原则:在if-else语句块中,如果if或else中的一个包含了多行,另一个为了对称性原则,也要用花括号(Exception 2: Brace symmetry: Use braces also in if-then-else blocks where either the if-code or the else-code covers several lines:)
// Wrong
if (address.isEmpty())
qDebug("empty!");
else {
qDebug("%s", qPrintable(address));
it;
}
// Correct
if (address.isEmpty()) {
qDebug("empty!");
} else {
qDebug("%s", qPrintable(address));
it;
}
// Wrong
if (a)
…
else
if (b)
…
// Correct
if (a) {
…
} else {
if (b)
…
}
- 当条件语句的执行体是空语句的时候,用一个花括号(Use curly braces when the body of a conditional statement is empty)
// Wrong
while (a);
// Correct
while (a) {}
圆括号 Parentheses
- 圆括号用来给语句分组(Use parentheses to group expressions:)
// Wrong
if (a && b || c)
// Correct
if ((a && b) || c)
// Wrong
a + b & c
// Correct
(a + b) & c
switch 语句 Switch statements
- case标签和switch在同一列(The case labels are in the same column as the switch)
- 每一个case语句的末尾都要有一个break语句或return语句,除非因功能需要故意不加或另外一个case是紧跟上一个case的。
switch (myEnum) {
case Value1:
doSomething();
break;
case Value2:
case Value3:
doSomethingElse();
Q_FALLTHROUGH();
default:
defaultHandling();
break;
}
跳转语句 (break, continue, return, and goto)
- 不要在跳转关键词后边加else
// Wrong
if (thisOrThat)
return;
else
somethingElse();
// Correct
if (thisOrThat)
return;
somethingElse();
- 例外:如果这段代码是固有的对称结构,用else实现视觉上的对称也是可以的
换行 Line breaks
- 每行代码不多于100个字符;若有必要,(Keep lines shorter than 100 characters; wrap if necessary)
- 注释/apidoc行的实际文字应该保持80列以内。适应周围环境,并尝试以避免“锯齿状”段落的方式排列文本。(Comment/apidoc lines should be kept below 80 columns of actual text. Adjust to the surroundings, and try to flow the text in a way that avoids "jagged" paragraphs.)
- 逗号位于换行的末尾;操作员从新线路的开头开始。如果编辑器太窄,则很容易错过行尾的运算符。(Commas go at the end of wrapped lines; operators start at the beginning of the new lines. An operator at the end of the line is easy to miss if the editor is too narrow.)
// Wrong
if (longExpression +
otherLongExpression +
otherOtherLongExpression) {
}
// Correct
if (longExpression
+ otherLongExpression
+ otherOtherLongExpression) {
}
一般例外 General exceptions
- 当严格遵守规则使您的代码看起来很糟糕时,请随时破坏它。(When strictly following a rule makes your code look bad, feel free to break it.)
- 如果任何给定模块存在争议,维护者对接受的风格拥有最终决定权(根据The Qt Governance Model)。(If there is a dispute in any given module, the Maintainer has the final say on the accepted style (as per The Qt Governance Model).)
Artistic Style
以下选项可以用来格式化你的代码
--style=kr
--indent=spaces=4
--align-pointer=name
--align-reference=name
--convert-tabs
--attach-namespaces
--max-code-length=100
--max-instatement-indent=120
--pad-header
--pad-oper
Note that "unlimited" --max-instatement-indent is used only because astyle is not smart enough to wrap the first argument if subsequent lines would need indentation limitation. You are encouraged to manually limit in-statement-indent to roughly 50 colums:
int foo = some_really_long_function_name(and_another_one_to_drive_the_point_home(
first_argument, second_argument, third_arugment));
clang-format
你可以使用clang-format和git-clang-format来重新格式化你的代码。qt5的git仓库提供了一个 _clang-format的文件来设置Qt代码的格式规范。你可以把它拷贝到你的根目录并让clang-format使用它。
# Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
#
# You may use this file under the terms of the 3-clause BSD license.
# See the file LICENSE from this package for details.
# This is the clang-format configuration style to be used by Qt,
# based on the rules from https://wiki.qt.io/Qt_Coding_Style and
# https://wiki.qt.io/Coding_Conventions
---
# Webkit style was loosely based on the Qt style
BasedOnStyle: WebKit
Standard: c++17
# Column width is limited to 100 in accordance with Qt Coding Style.
# https://wiki.qt.io/Qt_Coding_Style
# Note that this may be changed at some point in the future.
ColumnLimit: 100
# How much weight do extra characters after the line length limit have.
# PenaltyExcessCharacter: 4
# Disable reflow of some specific comments
# qdoc comments: indentation rules are different.
# Translation comments and SPDX license identifiers are also excluded.
CommentPragmas: "^!|^:|^ SPDX-License-Identifier:"
# We want a space between the type and the star for pointer types.
PointerBindsToType: false
# We use template< without space.
SpaceAfterTemplateKeyword: false
# We want to break before the operators, but not before a '='.
BreakBeforeBinaryOperators: NonAssignment
# Braces are usually attached, but not after functions or class declarations.
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
# When constructor initializers do not fit on one line, put them each on a new line.
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# Indent initializers by 4 spaces
ConstructorInitializerIndentWidth: 4
# Indent width for line continuations.
ContinuationIndentWidth: 8
# No indentation for namespaces.
NamespaceIndentation: None
# Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125
IndentPPDirectives: AfterHash
# We only indent with 2 spaces for preprocessor directives
PPIndentWidth: 2
# Horizontally align arguments after an open bracket.
# The coding style does not specify the following, but this is what gives
# results closest to the existing code.
AlignAfterOpenBracket: true
AlwaysBreakTemplateDeclarations: true
# Ideally we should also allow less short function in a single line, but
# clang-format does not handle that.
AllowShortFunctionsOnASingleLine: Inline
# The coding style specifies some include order categories, but also tells to
# separate categories with an empty line. It does not specify the order within
# the categories. Since the SortInclude feature of clang-format does not
# re-order includes separated by empty lines, the feature is not used.
SortIncludes: false
# macros for which the opening brace stays attached.
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]
# Break constructor initializers before the colon and after the commas.
BreakConstructorInitializers: BeforeColon
# Add "// namespace <namespace>" comments on closing brace for a namespace
# Ignored for namespaces that qualify as a short namespace,
# see 'ShortNamespaceLines'
FixNamespaceComments: true
# Definition of how short a short namespace is, default 1
ShortNamespaceLines: 1
# When escaping newlines in a macro attach the '\' as far left as possible, e.g.
##define a \
# something; \
# other; \
# thelastlineislong;
AlignEscapedNewlines: Left
# Avoids the addition of a space between an identifier and the
# initializer list in list-initialization.
SpaceBeforeCpp11BracedList: false
代码规范 Coding Conventions
本节翻译自:https://wiki.qt.io/Coding_Conventions
本文是我们在编写Qt代码时所遵循的高级编码规范的概述。有关更低级别的规范,请参阅Qt编码风格;有关更高级别的指导原则,请参阅API设计原则。对于QML,请参阅QML编码规范;对于示例,请参阅[Qt示例指南(https://wiki.qt.io/Qt_Examples_Guidelines)。
C++特性 C++ features
- 不使用异常 Don't use exceptions
- 不使用运行时类型信息 Don't use rtti (Run-Time Type Information; that is, the
typeinfostruct, thedynamic_castor thetypeidoperators, including throwing exceptions) - 适当使用模板 Use templates wisely, not just because you can.
Hint: Use the compile autotest to see whether a C++ feature is supported by all compilers in the test farm.
Qt源代码中的规范 Conventions in Qt source code
- 所有源代码使用UTF-8编码 All source code is UTF-8
- 所有QObject子类必须使用
Q_OBJECT宏 Every QObject subclass must have aQ_OBJECTmacro, even if it doesn't have signals or slots, otherwiseqobject_castwill fail. - 在connect语句中规范化信号/槽的参数(请参阅QMetaObject::normalizedSignature),以更快地进行信号/槽查找。您可以使用qtrepotools/util/normalize规范化现有代码。Normalize the arguments for signals + slots (see
QMetaObject::normalizedSignature) inside connect statements to get faster signal/slot lookups. You can useqtrepotools/util/normalizeto normalize existing code.
包含保护 Include guards
- always use conventional include guards in public and private library headers:
// in qclassname.h
#ifndef QCLASSNAME_H
#define QCLASSNAME_H
// public declarations
#endif // QCLASSNAME_H
// in qclassname_p.h
#ifndef QCLASSNAME_P_H
#define QCLASSNAME_P_H
// private declarations
#endif // QCLASSNAME_P_H
- in headers that are part of tools, examples and demos, or tests, and never included by Qt user code, you may use
#pragma onceinstead
原因:#pragma once 定义不够明确,并且不属于标准。我们无法对 Qt 的安装方式、作为大型 SDK 的一部分的使用方式等做出任何假设。为了避免用户安装时出现问题,我们使用保守方法。为了避免冲突,请确保新类和文件名在所有 Qt 子模块中都是唯一的。
Rationale: #pragma once is not well enough defined, and not part of the standard. We cannot make any assumptions about how Qt is installed, used as part of a larger SDK etc. To avoid issues in user installations, we use the conservative approach. To avoid clashes, make sure that new class and file names are unique within all of Qt's submodules.
Including headers
-
In public header files, always use this form to include Qt headers:
#include <QtCore/qwhatever.h>. The library prefix is necessary for Mac OS X frameworks and is very convenient for non-qmake projects. -
在源码中,先包含专用头文件,再包含通用头文件,中间使用空行分隔 In source files, include specialized headers first, then generic headers. Separate the categories with empty lines.
-
#include <qstring.h> // #include Qt stuff // ... #include <new> // #include STL stuff // ... #include <limits.h> // #include system stuff // ... -
总是优先包含
qplatformdefs.h头文件 If you need to includeqplatformdefs.h, always include it as the first header file. -
If you need to include private headers, be careful. Use the following syntax, irrespective of which module or directory whatever_p.h is in.
-
#include <private/whatever_p.h>
类型转换 Casting
-
Avoid C casts, prefer C++ casts (static_cast, const_cast, reinterpret_cast)
- Rationale: Both
reinterpret_castand C-style casts are dangerous, but at leastreinterpret_castwon't remove the const modifier
- Rationale: Both
-
Don't use
dynamic_cast, useqobject_castfor QObjects or refactor your design, for example by introducing atype()method (seeQListWidgetItem) -
使用构造函数强制转换简单类型 Use the constructor to cast simple types:
int(myFloat)instead of(int)myFloat- Rationale: When refactoring code, the compiler will instantly let you know if the cast would become dangerous.
特定于编译器/平台的问题 Compiler/Platform specific issues
- Be extremely careful when using the questionmark operator. If the returned types aren't identical, some compilers generate code that crashes at runtime (you won't even get a compiler warning).
QString s;
return condition ? s : "nothing"; // crash at runtime - QString vs. const char *
- Be extremely careful about alignment.
- Whenever a pointer is cast such that the required alignment of the target is increased, the resulting code might crash at runtime on some architectures. For example, if a
const char *is cast to anconst int *, it'll crash on machines where integers have to be aligned at two- or four-byte boundaries. - Use a union to force the compiler to align variables correctly. In the example below, you can be sure that all instances of
AlignHelperare aligned at integer-boundaries.
- Whenever a pointer is cast such that the required alignment of the target is increased, the resulting code might crash at runtime on some architectures. For example, if a
union AlignHelper {
char c;
int i;
};
- 任何具有构造函数或需要运行代码进行初始化的对象都不能用作库代码中的全局对象,因为在运行该构造函数/代码时(在首次使用时,在库加载时,在main()之前或之后,它都是未定义的)。即使为共享库定义了初始化程序的执行时间,在插件中移动该代码或静态编译库时也会遇到麻烦 Anything that has a constructor or needs to run code to be initialized cannot be used as global object in library code, since it is undefined when that constructor/code will be run (on first usage, on library load, before main() or not at all). Even if the execution time of the initializer is defined for shared libraries, you'll get into trouble when moving that code in a plugin or if the library is compiled statically:
// global scope
static const QString x; // Wrong - default constructor needs to be run to initialize x
static const QString y = "Hello"; // Wrong - constructor that takes a const char * has to be run
QString z; // super wrong
static const int i = foo(); // wrong - call time of foo() undefined, might not be called at all
- Things you can do:
// global scope
static const char x[] = "someText"; // Works - no constructor must be run, x set at compile time
static int y = 7; // Works - y will be set at compile time
static MyStruct s = {1, 2, 3}; // Works - will be initialized statically, no code being run
static QString *ptr = 0; // Pointers to objects are ok - no code needed to be run to initialize ptr
- Use
Q_GLOBAL_STATICto create static global objects instead:
Q_GLOBAL_STATIC(QString, s)
void foo()
{
s()->append("moo");
}
-
Note: Static objects in a scope are no problem, the constructor will be run the first time the scope is entered. Such code is reentrant since C++11.
-
A
charis signed or unsigned dependent on the architecture. Usesigned charorunsigned charif you explicitely want a signed/unsigned char. The condition in the following code is always true on platforms where the default char is unsigned.
char c; // c can't be negative if it is unsigned
/********/
/*******/
if (c > 0) { … } // WRONG - condition is always true on platforms where the default is unsigned
-
Avoid 64-bit enum values.
- The aapcs embedded ABI hard codes all enum values to a 32-bit integer.
- Microsoft compilers don't support 64-bit enum values. (confirmed with Microsoft ® C/C++ Optimizing Compiler Version 15.00.30729.01 for x64)
美学 Aesthetics
-
宁可使用enum来定义常量,也不要使用
static const int或define。Prefer enums to define constants overstatic const intor defines.- enum values will be replaced by the compiler at compile time, resulting in faster code
- defines are not namespace safe (and look ugly)
-
建议参数名字需要完整表达。Prefer verbose argument names in headers.
- Most IDEs will show the argument names in their completion-box.
- It will look better in the documentation
- Bad style:
doSomething(QRegion rgn, QPoint p)- usedoSomething(QRegion clientRegion, QPoint gravitySource)instead
-
When reimplementing a virtual method, do not put the
virtualkeyword in the header file.
On Qt5, annotate them with the override keyword after the function declaration, just before the ';' (or the '{' ).
需要避免的事 Things to avoid
-
Do not inherit from template/tool classes
- The destructors are not virtual, leading to potential memory leaks
- The symbols are not exported (and mostly inline), leading to interesting symbol clashes.
- Example: Library A has
class Q_EXPORT X: public QList<QVariant> {};and library B has
class Q_EXPORT Y: public QList<QVariant> {};Suddenly, QList
's symbols are exported from two libraries - /***/. -
不要混合使用const和非const迭代器 Don't mix const and non-const iterators. This will silently crash on broken compilers.
-
for (Container::const_iterator it = c.begin(); it != c.end(); ++it) // W R O N G for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it) // Right -
Q[Core]Application is a singleton class. There can only be one instance at a time. However, that instance can be destroyed and a new one can be created, which is likely in an ActiveQt or browser plugin. Code like this will easily break:
static QObject *obj = 0; if (!obj) obj = new QObject(QCoreApplication::instance());If the
QCoreApplicationapplication is destroyed,objwill be a dangling pointer. UseQ_GLOBAL_STATICfor static global objects orqAddPostRoutineto clean up. -
Avoid the use of anonymous namespaces in favor of the static keyword if possible. A name localized to the compilation unit with
staticis guaranteed to have internal linkage. For names declared in anonymous namespaces the C++ standard unfortunately mandates external linkage. (7.1.1/6, or see various discussions about this on the gcc mailing lists)
二进制兼容性和源代码兼容性 Binary and Source Compatibility
- Definitions:
- Qt 4.0.0 is a major release, Qt 4.1.0 is a minor release, Qt 4.1.1 is a patch release
- 向后二进制兼容性:链接到早期版本库的代码保持正常工作。Backward binary compatibility: Code linked to an earlier version of the library keeps working
- 向前的二进制兼容性:链接到新版本库的代码可与旧库一起使用。Forward binary compatibility: Code linked to a newer version of the library works with an older library
- Source code compatibility: Code compiles without modification
- Keep backward binary compatibility + backward source code compatibility in minor releases
- Keep backward and forward binary compatibility + forward and backward source code compatibility in patch releases
- Don't add/remove any public API (e.g. global functions, public/protected/private methods)
- Don't reimplement methods (not even inlines, nor protected/private methods)
- Check Binary Compatibility Workarounds for ways to keep b/c
- Info on binary compatibility: https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++
- When writing a ·QWidget· subclass, always reimplement ·event()·, even if it's empty. This makes sure that the widget can be fixed without breaking binary compatibility.
- All exported functions from Qt must start with either 'q' or 'Q'. Use the "symbols" autotest to find violations.
命名空间 Namespacing
Read Qt In Namespace and keep in mind that all of Qt except Tests and Webkit is "namespaced" code.
操作符 Operators
"The decision between member and non-member"
A binary operator that treats both of its arguments equally should not be a member. Because, in addition to the reasons mentioned in the stack overflow answer, the arguments are not equal when the operator is a member.
Example with QLineF which unfortunately has its operator== as a member:
QLineF lineF;
QLine lineN;
if (lineF == lineN) // Ok, lineN is implicitly converted to QLineF
if (lineN == lineF) // Error: QLineF cannot be converted implicitly to QLine, and the LHS is a member so no conversion applies
If the operator== was outside of the class, conversion rules would apply equally for both sides.
公共头文件的规范 Conventions for public header files
Our public header files have to survive the strict settings of some of our users. All installed headers have to follow these rules:
-
No C style casts (
-Wold-style-cast)- Use
static_cast,const_castorreinterpret_cast - for basic types, use the constructor form:
int(a)instead of(int)a - See chapter "Casting" for more info
- Use
-
No float comparisons (
-Wfloat-equal)- Use
qFuzzyCompareto compare values with a delta - Use
qIsNullto check whether a float is binary 0, instead of comparing it to 0.0.
- Use
-
Don't hide virtual methods in subclasses (
-Woverloaded-virtual)- If the baseclass A has a
virtual int val()and subclass B an overload with the same name,int val(int x), A'svalfunction is hidden. Use theusingkeyword to make it visible again:class B: public A { using A::val; int val(int x); };
- If the baseclass A has a
-
Don't shadow variables (
-Wshadow)- avoid things like
this->x = x; - don't give variables the same name as functions declared in your class
- avoid things like
-
Always check whether a preprocessor variable is defined before probing its value (
-Wundef)#if Foo == 0 // W R O N G #if defined(Foo) && (Foo == 0) // Right #if Foo - 0 == 0 // Clever, are we? Use the one above instead, for better readability
C++11使用规范 Conventions for C++11 usage
Note: This section is not an accepted convention yet. This section serves as baseline for further discussions.
Lambdas
You can use lambdas with the following restrictions:
-
If you use static functions from the class that the lambda is located in, refactor the code so you don't use a lambda. For example, instead of
void Foo::something() { ... std::generate(begin, end, []() { return Foo::someStaticFunction(); }); ... }You may be able to simply pass the function pointer:
void Foo::something() { ... std::generate(begin, end, &Foo::someStaticFunction); ... }The reason for this is that GCC 4.7 and earlier had a bug that required capturing
thisbut Clang 5.0 and later will produce a warning if you do:void Foo::something() { ... std::generate(begin, end, [this]() { return Foo::someStaticFunction(); }); // warning: lambda capture 'this' is not used [-Wunused-lambda-capture] ... }
Format the lambda according to the following rules:
-
Always write parentheses for the parameter list, even if the function does not take parameters.
[]() { doSomething(); }-NOT
[] { doSomething(); } -
Place the capture-list, parameter list, return type, and opening brace on the first line, the body indented on the following lines, and the closing brace on a new line.
[]() -> bool { something(); return isSomethingElse(); }-NOT-
[]() -> bool { something(); somethingElse(); } -
Place a closing parenthesis and semicolon of an enclosing function call on the same line as the closing brace of the lambda.
foo([]() { something(); }); -
If you are using a lambda in an 'if' statement, start the lambda on a new line, to avoid confusion between the opening brace for the lambda and the opening brace for the 'if' statement.
if (anyOf(fooList, [](Foo foo) { return foo.isGreat(); })) { return; }-NOT-
if (anyOf(fooList, [](Foo foo) { return foo.isGreat(); })) { return; } -
Optionally, place the lambda completely on one line if it fits.
foo([]() { return true; }); if (foo([]() { return true; })) { ... }
auto Keyword
Optionally, you can use the auto keyword in the following cases. If in doubt, for example if using auto could make the code less readable, do not use auto. Keep in mind that code is read much more often than written.
-
When it avoids repetition of a type in the same statement.
auto something = new MyCustomType; auto keyEvent = static_cast<QKeyEvent *>(event); auto myList = QStringList() << QLatin1String("FooThing") << QLatin1String("BarThing"); -
When assigning iterator types.
auto it = myList.const_iterator();
API设计原则 API Design Principles
本节翻译自:https://wiki.qt.io/API_Design_Principles
One of Qt's most reputed merits is its consistent, easy-to-learn, powerful API. This document tries to summarize the know-how we've accumulated on designing Qt-style APIs. Many of the guidelines are universal; others are more conventional, and we follow them primarily for consistency with existing APIs.
Although these guidelines are aimed primarily at public APIs, you are encouraged to use the same techniques when designing internal APIs, as a courtesy to your fellow developers.
You may also be interested to read Jasmin Blanchette's Little Manual of API Design (PDF) or its predecessor Designing Qt-Style C++ APIs by Matthias Ettrich.
优秀API的六大特性 Six Characteristics of Good APIs
An API is to the programmer what a GUI is to the end-user. The 'P' in API stands for "Programmer", not "Program", to highlight the fact that APIs are used by programmers, who are humans.
In his Qt Quarterly 13 article about API design, Matthias tells us he believes that APIs should be minimal and complete, have clear and simple semantics, be intuitive, be easy to memorize, and lead to readable code.
Be minimal
A minimal API is one that has as few public members per class and as few classes as possible. This makes it easier to understand, remember, debug, and change the API.
Be complete
A complete API means the expected functionality should be there. This can conflict with keeping it minimal. Also, if a member function is in the wrong class, many potential users of the function won't find it.
Have clear and simple semantics
As with other design work, you should apply the principle of least surprise. Make common tasks easy. Rare tasks should be possible but not the focus. Solve the specific problem; don't make the solution overly general when this is not needed. (For example, QMimeSourceFactory in Qt 3 could have been called QImageLoader and have a different API.)
Be intuitive
As with anything else on a computer, an API should be intuitive. Different experience and background leads to different perceptions on what is intuitive and what isn't. An API is intuitive if a semi-experienced user gets away without reading the documentation, and if a programmer who doesn't know the API can understand code written using it.
Be easy to memorize
To make the API easy to remember, choose a consistent and precise naming convention. Use recognizable patterns and concepts, and avoid abbreviations.
Lead to readable code
Code is written once, but read (and debugged and changed) many times. Readable code may sometimes take longer to write, but saves time throughout the product's life cycle.
Finally, keep in mind that different kinds of users will use different parts of the API. While simply using an instance of a Qt class should be intuitive, it's reasonable to expect the user to read the documentation before attempting to subclass it.
静态多态性 Static Polymorphism
Similar classes should have a similar API. This can be done using inheritance where it makes sense — that is, when run-time polymorphism is used. But polymorphism also happens at design time. For example, if you exchange a QProgressBar with a QSlider, or a QString with a QByteArray, you'll find that the similarity of APIs makes this replacement very easy. This is what we call "static polymorphism".
Static polymorphism also makes it easier to memorize APIs and programming patterns. As a consequence, a similar API for a set of related classes is sometimes better than perfect individual APIs for each class.
In general, in Qt, we prefer to rely on static polymorphism than on actual inheritance when there's no compelling reason to do otherwise. This keeps the number of public classes in Qt down and makes it easier for new Qt users to find their way around in the documentation.
Good
QDialogButtonBox and QMessageBox have similar APIs for dealing with buttons (addButton(), setStandardButtons(), etc.), without publicly inheriting from some "QAbstractButtonBox" class.
Bad
QAbstractSocket is inherited both by QTcpSocket and QUdpSocket, two classes with very different modes of interaction. Nobody seems to have ever used (or been able to use) a QAbstractSocket pointer in a generic and useful way.
模棱两可 Dubious
QBoxLayout is the base class of QHBoxLayout and QVBoxLayout. Advantage: Can use a QBoxLayout and call setOrientation() in a toolbar to make it horizontal/vertical. Disadvantages: One extra class, and possibility for users to write ((QBoxLayout *)hbox)->setOrientation(Qt::Vertical), which makes little sense.
基于属性的API Property-Based APIs
Newer Qt classes tend to have a "property-based API". E.g.:
QTimer timer;
timer.setInterval(1000);
timer.setSingleShot(true);
timer.start();
By property, we mean any conceptual attribute that's part of the object's state — whether or not it's an actual Q_PROPERTY. When practicable, users should be allowed to set the properties in any order; i.e., the properties should be orthogonal. For example, the preceding code could be written
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(1000);
timer.start();
For convenience, we can also write
timer.start(1000);
Similarly, for QRegExp, we have
QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern(".");
regExp.setPatternSyntax(Qt::WildcardSyntax);
To implement this type of API, it pays off to construct the underlying object lazily. E.g. in QRegExp's case, it would be premature to compile the "." pattern in setPattern() without knowing what the pattern syntax will be.
Properties often cascade; in that case, we must proceed carefully. Consider the "default icon size" provided by the current style vs. the "iconSize" property of QToolButton:
toolButton->iconSize(); // returns the default for the current style
toolButton->setStyle(otherStyle);
toolButton->iconSize(); // returns the default for otherStyle
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize(); // returns (52, 52)
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize(); // returns (52, 52)
Notice that once we set iconSize, it stays set; changing the current style doesn't change a thing. This is good. Sometimes, it's useful to be able to reset a property. Then there are two approaches:
- pass a special value (such as QSize(), –1, or Qt::Alignment(0)) to mean "reset"
- have an explicit resetFoo() or unsetFoo() function
For iconSize, it would be enough to make QSize() (i.e., QSize(–1, -1)) mean "reset".
In some cases, getters return something different than what was set. E.g. if you call widget->setEnabled(true), you might still get widget->isEnabled() return false, if the parent is disabled. This is OK, because that's usually what we want to check (a widget whose parent is disabled should be grayed out too and behave as if it were disabled itself, at the same time as it remembers that deep inside, it really is "enabled" and waiting for its parent to become enabled again), but must be documented properly.
QProperty
QProperty should be used instead of getter/setter/signal properties in all new code. If the QProperty can live in the public object, you can register it with the meta object system using Q_PROPERTY(
class Foo : public QObject
{
Q_OBJECT
Q_PROPERTY(int myInt)
public:
QProperty<int> myInt;
};
You don't need to specify any getters, setters, or signals. The public member has operator=(), and casts to its underlying type. Instead of change signals, bindings should be used. In cases where an "eager" change notification is unavoidable, QProperty::subscribe() should be used.
In case the property member has to live in the private object, you can use Q_PRIVATE_QPROPERTY to register it. Q_PRIVATE_QPROPERTY declares a struct with methods forwarding to the actual property. For example:
class Foo : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(Foo)
Q_PRIVATE_QPROPERTY(Foo::d_func(), int, myInt, setMyInt)
public:
Q_PRIVATE_QPROPERTIES_BEGIN
Q_PRIVATE_QPROPERTY_IMPL(myInt)
// possibly more properties
Q_PRIVATE_QPROPERTIES_END
};
... elsewhere ...
class FooPrivate
{
public:
QProperty<int> myInt;
};
Every public class with Q_OBJECT must have Q_PRIVATE_QPROPERTIES_BEGIN / Q_PRIVATE_QPROPERTIES_END, regardless of whether it has any properties, in case someone in the future wants to add a property.
被通知的属性 Notified Properties
If you need to retain change signals for backwards compatibility reasons, you can use QNotifiedProperty instead of QProperty. Mind that this leads to suboptimal binding evaluation, and should be avoided if possible. In particular, QProperty does have a subscribe() method you can use instead. subscribe() is also inferior to bindings, though.
A QNotifiedProperty accepts a callback that you can use to send the signal. Typically such a property's implementation will be in the private object. The callback has to be a member function of the class the property lives in. For example:
class Foo : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(Foo)
Q_PRIVATE_QPROPERTY(Foo::d_func(), int, myInt, setMyInt, NOTIFY myIntChanged)
public:
Q_PRIVATE_QPROPERTIES_BEGIN
Q_PRIVATE_QPROPERTY_IMPL(myInt)
// possibly more properties
Q_PRIVATE_QPROPERTIES_END
Q_SIGNALS:
void myIntChanged();
};
... elsewhere ...
class FooPrivate
{
Q_DECLARE_PUBLIC(Foo)
public:
void myIntChanged() { Q_EMIT q_func()->myIntChanged(); }
QNotifiedProperty<int, &FooPrivate::myIntChanged> myInt;
};
特殊情况 Special cases
You can use Q_PRIVATE_READONLY_PROPERTY to create a read-only accessor in the public object for a Q(Notified)Property in the private object.
In some cases you need to choose different names for the QProperty member and the property registered in the meta object system. This can be achieved with the NAME attribute to Q_PROPERTY and Q_PRIVATE_QPROPERTY. This mostly happens if a getter/setter/signal property contains a setter or getter that cannot be mapped to QProperty or QNotifiedProperty. For example, the setter may be virtual. In that case you may choose a different name for the QProperty and leave the original setter and getter in place.
C++特性 C++ Specifics
Value vs. Object
Pointers vs. References
Which is best for out-parameters, pointers or references?
void getHsv(int *h, int *s, int *v) const
void getHsv(int &h, int &s, int &v) const
Most C++ books recommend references whenever possible, according to the general perception that references are "safer and nicer" than pointers. In contrast, we at Qt Software tend to prefer pointers because they make the user code more readable. Compare:
color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);
Only the first line makes it clear that there's a high probability that h, s, and v will be modified by the function call.
That said, compilers really don't like out parameters, so you should avoid them in new APIs. Instead, return a small struct:
struct Hsv { int hue, saturation, value };
Hsv getHsv() const;
传常量引用 vs 传值 Passing by const-ref vs. Passing by value
If the type is bigger than 16 bytes, pass by const-ref.
If the type has a non-trivial copy-constructor or a non-trivial destructor, pass by const-ref to avoid executing these methods.
All other types should usually be passed by value.
Example:
void setAge(int age);
void setCategory(QChar cat);
void setName(QLatin1String name);
void setAlarm(const QSharedPointer<Alarm> &alarm); // const-ref is much faster than running copy-constructor and destructor
// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect are good examples of other classes you should pass by value.
虚函数 Virtual Functions
When a member function of a class is declared virtual in C++, it's primarily to allow customizing the behavior of the function through overriding it in a custom subclass. The purpose of making the function virtual is so existing calls to that function will visit your code path instead. If nobody outside of the class calls this function, you should be very careful before you declare it as virtual.
// QTextEdit in Qt 3: member functions that have no reason for being virtual
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat &f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursorc = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }
When QTextEdit was ported from Qt 3 to Qt 4, almost all virtual functions were removed. Interestingly (but not unexpected), there were no big complaints Why? Because Qt 3 didn't make use of polymorphism for QTextEdit; Qt 3 doesn't call these functions - you do. In short, there was no reason to subclass QTextEdit and reimplement these functions unless you called these functions yourself. If you needed polymorphism in your application outside of Qt, you would add polymorphism yourself.
避免虚函数 Avoiding virtual functions
In Qt, we try to minimize the number of virtual functions for a number of reasons. Each virtual call complicates bugfixing through inserting an uncontrolled node in the call graph (making the outcome somewhat unpredictable). People do crazy things from inside a reimplementation of a virtual function, such as:
- sending events
- emitting signals
- reentering the event loop (e.g., by opening a modal file dialog)
- deleting the object (i.e., somehow causing "delete this")
There are many other reasons to avoid excessive use of virtual functions:
- you cannot add, move or remove virtual functions without breaking backward compatibility
- you cannot easily override a virtual function
- compilers can almost never optimize or inline calls to virtual functions
- calling the function requires a v-table lookup, making it 2-3 times slower than a normal function
- virtual functions make the class hard to copy by value (possible, but very messy and discouraged)
Experience has taught us that a class with no virtual functions tends to have fewer bugs and generally causes less maintenance.
A general rule of thumb is that unless we as a toolkit and primary users of this class call that function, it should probably not be virtual.
虚函数特性与可复制性 Virtualness vs. copyability
Polymorphic objects and value-type classes are not good friends.
Classes with virtual functions must declare a virtual destructor to avoid memory leaks as the base class is destroyed without cleaning up data in the subclass.
If you want to be able to copy and assign to a class, or compare by value, you probably need a copy constructor, an assignment operator and an equals-operator.
class CopyClass {
public:
CopyClass();
CopyClass(const CopyClass &other);
~CopyClass();
CopyClass &operator=(const CopyClass &other);
bool operator== (const CopyClass &other) const;
bool operator!=(const CopyClass &other) const;
virtual void setValue(int v);
};
If you create subclasses of this class, unexpected things can start happening in your code. Normally, if there are no virtual functions and no virtual destructor, people cannot not create a subclass and rely on polymorphism. However if you add virtual functions, or a virtual destructor, there suddenly becomes a reason to create the subclass, and now things get complicated. At first glance it's easy to think you can simply declare virtual operators. But wandering down this path can and will lead to chaos and destruction (read: unreadable code). Studying the following example:
class OtherClass {
public:
const CopyClass &instance() const; // what does it return? What should I assign it to?
};
(this section is under construction)
常量性 Constness
C++ provides the keyword "const" to signify that something will not change or have side effects. This applies to simple values, to pointers and what's pointed to, and as a special attribute to functions that don't change the state of the object.
Note however that const does not provide much value in itself - many languages don't even provide any "const" keyword, but that doesn't automatically render them deficient for that reason. In fact, if you remove function overloads and use search and replace to remove all occurrances of the keyword "const" from your C++ source code, it's very likely to compile and work just fine. It's important to keep a pragmatic approach to the use of "const".
Let's walk through some areas that use "const" that are relevant to API design in Qt:
输入参数:常量指针 Input arguments: const pointers
Const functions that take input pointer arguments almost always take const pointer arguments.
If the function is really declared const, it means it will neither have side effects, nor alter the visible state of its object. So why should it require a non-const input argument? Remember that const functions are often called from within other const functions, and from there, non-const pointers are hard to come by (without a const_cast, and we really like to avoid const_cast where we can).
Before:
bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos) const;
QWidget declares many const functions that take non-const pointer input arguments. Note that the function is allowed to modify the widget, but not itself. Functions like these are often accompanied by const_casts. It would have been nice if these functions took const pointer arguments:
After:
bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos) const;
Note that we fixed this in QGraphicsItem, but QWidget must wait until Qt 5:
bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point) const;
返回值:常量值 Return values: const values
The result of calling a function that does not return a reference is an R-value.
Non-class R-values always have cv-unqualified type. So even if it is syntactically possible to add a "const" on them it does not make much sense as it won't change anything regarding access rights. Most modern compilers will print a warning when compiling such code.
When adding a "const" to a class type R-values access to non-const member functions is prohibited as well as direct manipulation of its members.
Not adding a "const" allows such access, but is rarely needed as the changes end with the life time of the R-value object, which will usually happen at the end of, loosely spoken, the next semicolon).
Example:
struct Foo
{
void setValue(int v) { value = v; }
int value;
};
Foo foo()
{
return Foo();
}
const Foo cfoo()
{
return Foo();
}
int main()
{
// The following does compile, foo() is non-const R-value which
// can't be assigned to (this generally requires an L-value)
// but member access leads to a L-value:
foo().value = 1; // Ok, but temporary will be thrown away at the end of the full-expression.
// The following does compile, foo() is non-const R-value which
// can't be assigned to, but calling (even non-const) member
// function is fine:
foo().setValue(1); // Ok, but temporary will be thrown away at the end of the full-expression.
// The following does _not_compile, foo() is ''const'' R-value
// with const member which member access can't be assigned to:
cfoo().value = 1; // Not ok.
// The following does _not_compile, foo() is ''const'' R-value,
// one cannot call non-const member functions:
cfoo().setValue(1); // Not ok
}
返回值:指针与常量指针 Return values: pointers vs. const pointers
On the subject of whether const functions should return pointers or const pointers, this is where most people find that the concept of "const correctness" falls apart in C++. The problem starts when const functions, which do not modify their own state, return a non-const pointer to a member. The simple act of returning this pointer does not affect the object's visible state, nor does it change the state of its responsibilities. But it does give the programmer indirect access to modify the object's data.
This example shows one of the many ways to circumvent constness using const functions that return non-const pointers:
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
moveBy(10, 10); // doesn't compile!
window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // compiles!
}
Functions that return const pointers do protect against this (perhaps unwanted / unexpected) side-effect, at least to a certain degree. But which functions would you prefer to return a const pointer, or a list thereof? If we take the const-correct approach, every const function that returns a pointer to one of its members (or a list-of-pointers-to-members), must return a const pointer. In practise this unfortunately leads to unusable APIs:
QGraphicsScene scene;
// … populate scene
foreach (const QGraphicsItem *item, scene.items()) {
item->setPos(qrand() % 500, qrand() % 500); // doesn't compile! item is a const pointer
}
QGraphicsScene::items()
is a const function, and this might lead you to think it should only return const pointers.
In Qt we use the non-const pattern almost exclusively. We've chosen a pragmatic approach: Returning const pointers is more likely to result in excessive use of const_cast than what problems arise from abusing non-const pointer return types.
返回值:按值返回还是按常量引用返回? Return values: by value or const reference?
If we hold a copy of the object to return, returning a const reference is the fastest approach; however, this restrains us later on if we want to refactor the class. (Using the d-pointer idiom, we can change the memory representation of Qt classes at any time; but we cannot change a function's signature from "const QFoo &" to "QFoo" without breaking binary compatibility.) For this reason, we generally return "QFoo" rather than "const QFoo &", except in a few cases where speed is critical and refactoring isn't an issue (e.g. QList::at()).
Const与对象的状态 Const vs. the state of an object
Const correctness is a vi-emacs discussion in C, because the topic is broken in several areas (such as pointer-based functions).
But the general rule is that a const function does not alter the visible state of a class. State means "me and my responsibilities". That's doesn't mean that non-const functions change their own private data members, nor that const functions cannot. But that the function is active, and has visible side effects. const functions in general do not have any visible side effects. Like:
QSize size = widget->sizeHint(); // const
widget->move(10, 10); // not const
A delegate is responsible for drawing onto something else. Its state includes its responsibilities, and therefore includes the state of what it draws upon. Asking it to draw does have side effects; it changes the appearance (and with that, the state) of the device it's painting on. Because of that, it does not make sense that paint() is const. Neither does it make sense that any of Interview's paint()s or QIcon's paint() are const. Nobody would call QIcon::paint() from inside a const function unless they explicily want to void the constness of that function. And in that case, an explicit const_cast is better.
// QAbstractItemDelegate::paint is const
void QAbstractItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
// QGraphicsItem::paint is not const
void QGraphicsItem::paint(QPainter &painter, const QStyleOptionGraphicsItem &option, QWidget &widget = 0)
The const keyword does no "work" for you. Consider removing them rather than having overloaded const/non-const versions of a function.
API语义与文档说明 API Semantics and Documentation
What should you do when you pass -1 to a function? etc…
Warnings/fatals/etc
APIs need quality assurance. The first revision is never right; you must test it. Make use cases by looking at code which uses this API and verify that the code is readable.
Other tricks include having somebody else use the API with or without documentation and documenting the class (both the class overview and the individual functions).
命名的艺术 The Art of Naming
Naming is probably the single most important issue when designing an API. What should the classes be called? What should the member functions be called?
通用命名规则 General Naming Rules
A few rules apply equally well to all kinds of names. First, as I mentioned earlier, do not abbreviate. Even obvious abbreviations such as "prev" for "previous" don't pay off in the long run, because the user must remember which words are abbreviated.
Things naturally get worse if the API itself is inconsistent; for example, Qt 3 has activatePreviousWindow() and fetchPrev(). Sticking to the "no abbreviation" rule makes it simpler to create consistent APIs.
Another important but more subtle rule when designing classes is that you should try to keep the namespace for subclasses clean. In Qt 3, this principle wasn't always followed. To illustrate this, we will take the example of a QToolButton. If you call name(), caption(), text(), or textLabel() on a QToolButton in Qt 3, what do you expect? Just try playing around with a QToolButton in Qt Designer:
- The name property is inherited from QObject and refers to an internal object name that can be used for debugging and testing.
- The caption property is inherited from QWidget and refers to the window title, which has virtually no meaning for QToolButtons, since they usually are created with a parent.
- The text property is inherited from QButton and is normally used on the button, unless useTextLabel is true.
- The textLabel property is declared in QToolButton and is shown on the button if useTextLabel is true.
In the interest of readability, name is called objectName in Qt 4, caption has become windowTitle, and there is no longer any textLabel property distinct from text in QToolButton.
Documenting is also a good way of finding good names when you get stuck: just try to document the item (class, function, enum value, etc.) and use your first sentence as inspiration. If you cannot find a precise name, this is often a sign that the item shouldn't exist. If everything else fails and you are convinced that the concept makes sense, invent a new name. This is, after all, how "widget", "event", "focus", and "buddy" came to be.
Naming Classes
Identify groups of classes instead of finding the perfect name for each individual class. For example, All the Qt 4 model-aware item view classes are suffixed with View (QListView, QTableView, and QTreeView), and the corresponding item-based classes are suffixed with Widget instead (QListWidget, QTableWidget, and QTreeWidget).
Naming Enum Types and Values
The guiding principle is to avoid name clashes between enum values and to ensure readability code with reasonable verbosity.
Enums in Qt/global namespace
New enums in the Qt namespace should always use scoped/strong enumerators by default. The scoping/strong typing ensures that there is no conflict if the same enum value name is used multiple times:
namespace Qt
{
enum class Color {
Blue,
Orange,
Yellow
};
enum class FavoriteColor {
Yellow,
Orange
};
}
Color yellow = Qt::Color::Yellow;
FavoriteColor yellow2 = Qt::FavoriteColor::Yellow;
yellow2 = Qt::Orange; // error
When using scoped enums additional naming rules (repeating of enum type name inside enum value name) for are not necessary.
Enums in classes
Enums inside a class do not have the same problem of names clashing, as they are already namespaced within the class.
There are still reasons to prefer scoped enums inside classes, but this should be decided on a case by case basis.
If the enum values have a clear relation to the parent class, prefer un-scoped enums:
class TouchPoint
{
enum State {
Pressed,
Held,
Released
};
};
// The context is clear when used outside the class
if (point.state() == TouchPoint::Pressed)
...
// As well as when used inside it
if (state() == Pressed)
...
Using scoped enums in this case would add redundant line noise:
if (point.state() == TouchPoint::State::Pressed)
...
if (state() == State::Pressed)
...
Note that the context where the enum is used, such as the name of the getter that returns the enum value, might be enough information to make a scoped enum redundant.
If the enum values do not have a natural relation to the class name, prefer scoped enums, e.g.:
class QSslCertificate
{
enum class PatternSyntax {
RegularExpression,
Wildcard,
FixedString
};
};
if (syntax == PatternSyntax::Wildcard)
...
Another option to avoid the name *** instead of scoped/strong enums is to embedded the enum type name into each enum value. This method was extensively used in Qt 4 before scoped/strong enums were available.
class Widget
{
enum Corner {
TopLeftCorner,
BottomRightCorner,
…
};
};
tabWidget->setCornerWidget(widget, Widget::TopLeftCorner);
Enums in flags
When enumerator values can be OR'd together and be used as flags, the traditional solution is to store the result of the OR in an int, which isn't type-safe. Qt offers a template class QFlags
By convention, we give the enum type a singular name (since it can only hold one flag at a time) and the "flags" type a plural name. For example:
enum RectangleEdge { LeftEdge, RightEdge, … };
typedef QFlags<RectangleEdge> RectangleEdges;
In some cases, the "flags" type has a singular name. In that case, the enum type is suffixed with Flag:
enum AlignmentFlag { AlignLeft, AlignTop, … };
typedef QFlags<AlignmentFlag> Alignment;
函数和参数的命名 Naming Functions and Parameters
The number one rule of function naming is that it should be clear from the name whether the function has side-effects or not. In Qt 3, the const function QString::simplifyWhiteSpace() violated this rule, since it returned a QString instead of modifying the string on which it is called, as the name suggests. In Qt 4, the function has been renamed QString::simplified().
Parameter names are an important source of information to the programmer, even though they don't show up in the code that uses the API. Since modern IDEs show them while the programmer is writing code, it's worthwhile to give decent names to parameters in the header files and to use the same names in the documentation.
布尔型获取器、设置器和属性的命名 Naming Boolean Getters, Setters, and Properties
Finding good names for the getter and setter of a bool property is always a special pain. Should the getter be called checked() or isChecked()? scrollBarsEnabled() or areScrollBarEnabled()?
In Qt 4, we used the following guidelines for naming the getter function:
-
Adjectives are prefixed with
is-
. Examples:
isChecked()isDown()isEmpty()isMovingEnabled()
-
However, adjectives applying to a plural noun have no prefix:
scrollBarsEnabled(), not areScrollBarsEnabled()
-
Verbs have no prefix and don't use the third person (-s):
acceptDrops(), not acceptsDrops()allColumnsShowFocus()
-
Nouns generally have no prefix:
autoCompletion(), not isAutoCompletion()boundaryChecking()
-
Sometimes, having no prefix is misleading, in which case we prefix with
is-
:
isOpenGLAvailable(), not openGL()isDialog(), not dialog()
The name of the setter is derived from that of the getter by removing any is prefix and putting a set at the front of the name; for example, setDown() and setScrollBarsEnabled(). The name of the property is the same as the getter, but without the is prefix.
避免常见陷阱 Avoiding Common Traps
The Convenience Trap
It is a common misconception that the less code you need to achieve something, the better the API. Keep in mind that code is written more than once but has to be understood over and over again. For example,
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
is much harder to read (and even to write) than
QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");
The Boolean Parameter Trap
Boolean parameters often lead to unreadable code. In particular, it's almost invariably a mistake to add a bool parameter to an existing function. In Qt, the traditional example is repaint(), which takes an optional bool parameter specifying whether the background should be erased (the default) or not. This leads to code such as
widget->repaint(false);
which beginners might read as meaning, "Don't repaint!"
The thinking is apparently that the bool parameter saves one function, thus helping reducing the bloat. In truth, it adds bloat; how many Qt users know by heart what each of the next three lines does?
widget->repaint();
widget->repaint(true);
widget->repaint(false);
A somewhat better API might have been
widget->repaint();
widget->repaintWithoutErasing();
In Qt 4, we solved the problem by simply removing the possibility of repainting without erasing the widget. Qt 4's native support for double buffering made this feature obsolete.
Here are a few more examples:
widget->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_''''''.c??", false, true);
An obvious solution is to replace the bool parameters with enum types. This is what we've done in Qt 4 with case sensitivity in QString. Compare:
str.replace("USER", user, false); // Qt 3
str.replace("USER", user, Qt::CaseInsensitive); // Qt 4
The Copy Cat Trap
案例学习 Case Studies
QProgressBar
To show some of these concepts in practice, we'll study the QProgressBar API of Qt 3 and compare it to the Qt 4 API. In Qt 3:
class QProgressBar : public QWidget
{
…
public:
int totalSteps() const;
int progress() const;
const QString &progressString() const;
bool percentageVisible() const;
void setPercentageVisible(bool);
void setCenterIndicator(bool on);
bool centerIndicator() const;
void setIndicatorFollowsStyle(bool);
bool indicatorFollowsStyle() const;
public slots:
void reset();
virtual void setTotalSteps(int totalSteps);
virtual void setProgress(int progress);
void setProgress(int progress, int totalSteps);
protected:
virtual bool setIndicator(QString &progressStr,
int progress,
int totalSteps);
…
};
The API is quite complex and inconsistent; for example, it's not clear from the naming that reset(), setTotalSteps(), and setProgress() are tightly related.
The key to improve the API is to notice that QProgressBar is similar to Qt 4's QAbstractSpinBox class and its subclasses, QSpinBox, QSlider and QDial. The solution? Replace progress and totalSteps with minimum, maximum and value. Add a valueChanged() signal. Add a setRange() convenience function.
The next observation is that progressString, percentage and indicator really refer to one thing: the text that is shown on the progress bar. Usually the text is a percentage, but it can be set to anything using the setIndicator() function. Here's the new API:
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
By default, the text is a percentage indicator. This can be changed by reimplementing text().
The setCenterIndicator() and setIndicatorFollowsStyle() functions in the Qt 3 API are two functions that influence alignment. They can advantageously be replaced by one function, setAlignment():
void setAlignment(Qt::Alignment alignment);
If the programmer doesn't call setAlignment(), the alignment is chosen based on the style. For Motif-based styles, the text is shown centered; for other styles, it is shown on the right hand side.
Here's the improved QProgressBar API:
class QProgressBar : public QWidget
{
…
public:
void setMinimum(int minimum);
int minimum() const;
void setMaximum(int maximum);
int maximum() const;
void setRange(int minimum, int maximum);
int value() const;
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
Qt::Alignment alignment() const;
void setAlignment(Qt::Alignment alignment);
public slots:
void reset();
void setValue(int value);
signals:
void valueChanged(int value);
…
};
QAbstractPrintDialog & QAbstractPageSizeDialog
Qt 4.0 saw the apparition of two classes QAbstractPrintDialog and QAbstractPageSizeDialog that served as base classes for QPrintDialog and QPageSizeDialog. This served no purpose at all, since none of Qt's APIs take a QAbstractPrint- or -PageSizeDialog pointer as an argument and perform some operation on it. Using qdoc trickery, we've hidden them, but they're the prototypical examples of needless abstract classes.
This is not to say good abstraction is wrong, and indeed QPrintDialog probably should have a factory or some other mechanism for changing it- as evidenced by the #ifdef QTOPIA_PRINTDIALOG in its declaration.
QAbstractItemModel
The details of the problems with model/view in Qt 4 are documented well elsewhere, but an important generalization is that "QAbstractFoo" should not just be the union of all possible subclasses you can think of at the time of writing. Such "union of all things" base classes are almost never a good solution. QAbstractItemModel commits this error - it is really just QTreeOfTablesModel, with the consequently complicated API that causes… and which is then inherited by all the nicer subclasses,
Just adding abstraction does not make an API better automatically.
QLayoutIterator & QGLayoutIterator
In Qt 3, creating a custom layout involved subclassing both QLayout and QGLayoutIterator ("G" stands for generic). A QGLayoutIterator subclass instance pointer was wrapped in a QLayoutIterator, which users could use like any other iterator class. QLayoutIterator made it possible to write code like this:
QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
if (child->widget() == myWidget) {
it.takeCurrent();
return;
}
++it;
}
In Qt 4, we killed QGLayoutIterator classes (and their internal subclasses for box and grid layouts) and instead asked the QLayout subclasses to reimplement itemAt(), takeAt(), and count().
QImageSink
Qt 3 had a whole set of classes that allowed images to be incrementally read and passed to an animation - the QImageSource/Sink/QASyncIO/QASyncImageIO classes. Since all these were ever used for was animated QLabels, it was total overkill.
The lesson is not to add abstraction to aide some very vague future possibility. Keep it simple. When those future things come, it will be a lot easier to factor them into a simple system than into a complex one.
other Qt3 vs. Qt4?
QWidget::setWindowModified(bool)
Q3Url vs. QUrl
Q3TextEdit vs. QTextEdit
How all those virtual functions went a-goner…
Qt's Clipping Story (naming of clipping fns)
When you set the clip rect, you actually set a region (should be setClipRegion(QRect) instead of setClipRect()).
(on the right, how it should have been…)
参考
Qt Coding Style
Coding Conventions
UI Text Conventions
API Design Principles
Google C++ Style Guide
Designing Qt-Style C++ APIs
Policies/Binary Compatibility Issues With C++
D-Pointer

浙公网安备 33010602011771号