你最喜欢的 C++ 编码风格习语是什么
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/276173/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are your favorite C++ Coding Style idioms
提问by Foredecker
What are your favorite C++ coding style idioms? I'm asking about style or coding typography such as where you put curly braces, are there spaces after keywords, the size of indents, etc. This is opposed to best-practices or requirements such as always deleting arrays with delete[]
.
你最喜欢的 C++ 编码风格习语是什么?我问的是样式或编码排版,例如花括号的位置、关键字后是否有空格、缩进的大小等。这与最佳实践或要求相反,例如始终删除带有delete[]
.
Here is an example of one of my favorites: In C++ Class initializers, we put the separators at the front of the line, rather than the back. This makes it easier to keep this up to date. It also means that source code control diffs between versions are cleaner.
这是我最喜欢的一个示例:在 C++ 类初始值设定项中,我们将分隔符放在行的前面,而不是后面。这样可以更轻松地保持最新状态。这也意味着版本之间的源代码控制差异更清晰。
TextFileProcessor::
TextFileProcessor( class ConstStringFinder& theConstStringFinder )
: TextFileProcessor_Base( theConstStringFinder )
, m_ThreadHandle ( NULL )
, m_startNLSearch ( 0 )
, m_endNLSearch ( 0 )
, m_LineEndGetIdx ( 0 )
, m_LineEndPutIdx ( 0 )
, m_LineEnds ( new const void*[ sc_LineEndSize ] )
{
;
}
采纳答案by kshahar
When creating enumerations, put them in a namespace so that you can access them with a meaningful name:
创建枚举时,将它们放在命名空间中,以便您可以使用有意义的名称访问它们:
namespace EntityType {
enum Enum {
Ground = 0,
Human,
Aerial,
Total
};
}
void foo(EntityType::Enum entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
EDIT: However, this technique has become obsolete in C++11. Scoped enumeration(declared with enum class
or enum struct
) should be used instead: it is more type-safe, concise, and flexible. With old-style enumerations the values are placed in the outer scope. With new-style enumeration they are placed within the scope of the enum class
name.
Previous example rewritten using scoped enumeration (also known as strongly typed enumeration):
编辑:但是,这种技术在 C++11 中已经过时了。应该使用作用域枚举(用enum class
或声明enum struct
):它更类型安全、更简洁、更灵活。使用旧式枚举,值放置在外部作用域中。使用新式枚举,它们被放置在enum class
名称的范围内。
使用作用域枚举(也称为强类型枚举)重写前一个示例:
enum class EntityType {
Ground = 0,
Human,
Aerial,
Total
};
void foo(EntityType entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
There are other significant benefits from using scoped enumerations: absence of implicit cast, possible forward declaration, and ability to use custom underlying type (not the default int
).
使用作用域枚举还有其他显着的好处:没有隐式强制转换、可能的前向声明以及使用自定义基础类型(不是默认值int
)的能力。
回答by jalf
RAII: Resource Acquisition Is Initialization
RAII:资源获取即初始化
RAII may be the most important idiom. It is the idea that resources should be mapped to objects, so that their lifetimes are managed automatically according to the scope in which those objects are declared.
RAII 可能是最重要的习语。资源应该映射到对象,以便根据声明这些对象的范围自动管理它们的生命周期。
For example, if a file handle was declared on the stack, it should be implicitly closed once we return from the function (or loop, or whichever scope it was declared inside). If a dynamic memory allocation was allocated as a member of a class, it should be implicitly freed when that class instance is destroyed. And so on. Every kind of resource—memory allocations, file handles, database connections, sockets, and any other kind of resource that has to be acquired and released—should be wrapped inside such a RAII class, whose lifetime is determined by the scope in which it was declared.
例如,如果在堆栈上声明了一个文件句柄,一旦我们从函数(或循环,或它在内部声明的任何范围)返回,它就应该隐式关闭。如果动态内存分配是作为类的成员分配的,则在销毁该类实例时应该隐式释放它。等等。每一种资源——内存分配、文件句柄、数据库连接、套接字以及任何其他类型的必须获取和释放的资源——都应该被包装在这样一个 RAII 类中,它的生命周期由它所在的范围决定宣布。
One major advantage of this is that C++ guarantees that destructors are called when an object goes out of scope, regardless of how control is leaving that scope. Even if an exception is thrown, all local objects will go out of scope, and so their associated resources will get cleaned up.
这样做的一个主要优点是 C++ 保证在对象超出范围时调用析构函数,而不管控制如何离开该范围。即使抛出异常,所有本地对象也将超出范围,因此它们的相关资源将被清理。
void foo() {
std::fstream file("bar.txt"); // open a file "bar.txt"
if (rand() % 2) {
// if this exception is thrown, we leave the function, and so
// file's destructor is called, which closes the file handle.
throw std::exception();
}
// if the exception is not called, we leave the function normally, and so
// again, file's destructor is called, which closes the file handle.
}
Regardless of how we leave the function, and of what happens after the file is opened, we don't need to explicitly close the file, or handle exceptions (e.g. try-finally) within that function. Instead, the file gets cleaned up because it is tied to a local object that gets destroyed when it goes out of scope.
无论我们如何离开函数,以及文件打开后发生了什么,我们都不需要显式关闭文件,或在该函数中处理异常(例如 try-finally)。相反,文件会被清理,因为它与一个本地对象相关联,当它超出范围时会被销毁。
RAII is also less-commonly known as SBRM (Scope-Bound Resource Management).
RAII 也不太常见,称为 SBRM(范围绑定资源管理)。
See also:
也可以看看:
- ScopeGuardallows code to "automatically invoke an 'undo' operation .. in the event that an exception is thrown."
- ScopeGuard允许代码“在抛出异常时自动调用‘撤销’操作……”。
回答by RC.
Copy-swap
复制交换
The copy-swap idiom provides exception-safe copying. It requires that a correct copy ctor and swap are implemented.
复制交换习语提供异常安全复制。它要求实现正确的复制构造函数和交换。
struct String {
String(String const& other);
String& operator=(String copy) { // passed by value
copy.swap(*this); // nothrow swap
return *this; // old resources now in copy, released in its dtor
}
void swap(String& other) throw() {
using std::swap; // enable ADL, defaulting to std::swap
swap(data_members, other.data_members);
}
private:
Various data_members;
};
void swap(String& a, String& b) { // provide non-member for ADL
a.swap(b);
}
You can also implement the swap method with ADL (Argument Dependent Lookup) directly.
也可以直接用ADL(Argument Dependent Lookup)实现swap方法。
This idiom is important because it handles self-assignment[1], makes the strong exception guarantee[2], and is often very easy to write.
这个习惯用法很重要,因为它处理自赋值[1],提供强异常保证[2],并且通常很容易编写。
[1]Even though self-assignment isn't handled as efficiently as possible, it's supposed to be rare, so if it never happens, this is actually faster.
[1]尽管自赋值没有尽可能有效地处理,但它应该很少发生,所以如果它永远不会发生,这实际上更快。
[2]If any exception is thrown, the state of the object (*this
) is not modified.
[2]如果抛出任何异常,*this
则不会修改对象 ( )的状态。
回答by RC.
CRTP: Curiously Recurring Template Pattern
CRTP:奇怪的重复模板模式
CRTPhappens when you pass a class as a template parameter to its base class:
当您将一个类作为模板参数传递给其基类时,就会发生CRTP:
template<class Derived>
struct BaseCRTP {};
struct Example : BaseCRTP<Example> {};
Within the base class, it can get ahold of the derived instance, complete with the derived type, simply by casting (either static_castor dynamic_castwork):
在基类中,它可以通过强制转换(static_cast或dynamic_cast工作)获得派生实例,完成派生类型:
template<class Derived>
struct BaseCRTP {
void call_foo() {
Derived& self = *static_cast<Derived*>(this);
self.foo();
}
};
struct Example : BaseCRTP<Example> {
void foo() { cout << "foo()\n"; }
};
In effect, call_foohas been injectedinto the derived class with full access to the derived class's members.
实际上,call_foo已被注入到派生类中,并具有对派生类成员的完全访问权限。
Feel free to edit and add specific examples of use, possibly to other SO posts.
随意编辑和添加具体的使用示例,可能会添加到其他 SO 帖子中。
回答by jalf
pImpl: Pointer-to-Implementation
pImpl:实现的指针
The pImpl idiom is a very useful way to decouple the interface of a class from its implementation.
pImpl 惯用法是将类的接口与其实现分离的非常有用的方法。
Normally, a class definition must contain member variables as well as methods, which may expose too much information. For example, a member variable may be of a type defined in a header that we don't wish to include everywhere.
通常,类定义必须包含成员变量和方法,这可能会暴露过多的信息。例如,成员变量可能是在我们不希望在任何地方都包含的头文件中定义的类型。
The windows.h
header is a prime example here. We may wish to wrap a HANDLE
or another Win32 type inside a class, but we can't put a HANDLE
in the class definition without having to include windows.h
everywhere the class is used.
该windows.h
标题是这里最好的例子。我们可能希望将一个HANDLE
或另一个 Win32 类型包装在一个类中,但是我们不能HANDLE
在类定义中放置 a ,而不必在使用该类的任何windows.h
地方都包含它。
The solution then is to create a Private IMPLementation or Pointer-to-IMPLementation of the class, and let the public implementation store only a pointer to the private one, and forward all member methods.
那么解决的办法是建立一个Private IMPLementation或Pointer-TO- IMPL类的ementation,并让公众实现只存储一个指针到专用网络,并且转发所有成员方法。
For example:
例如:
class private_foo; // a forward declaration a pointer may be used
// foo.h
class foo {
public:
foo();
~foo();
void bar();
private:
private_foo* pImpl;
};
// foo.cpp
#include whichever header defines the types T and U
// define the private implementation class
class private_foo {
public:
void bar() { /*...*/ }
private:
T member1;
U member2;
};
// fill in the public interface function definitions:
foo::foo() : pImpl(new private_foo()) {}
foo::~foo() { delete pImpl; }
void foo::bar() { pImpl->bar(); }
The implementation of foo
is now decoupled from its public interface, so that
的实现foo
现在与其公共接口分离,以便
- it can use members and types from other headers without requiring these dependencies to be present when the class is used, and
- the implementation can be modified without forcing a recompile of the code that uses the class.
- 它可以使用来自其他标头的成员和类型,而无需在使用类时存在这些依赖项,并且
- 可以在不强制重新编译使用该类的代码的情况下修改实现。
Users of the class simply include the header, which contains nothing specific about the implementation of the class. All implementation details are contained inside foo.cpp
.
类的用户只包含标题,它不包含任何关于类实现的具体内容。所有实现细节都包含在foo.cpp
.
回答by Prembo
I like lining up code/initializations in 'columns'... Proves very useful when editing with a 'column' mode capable editor and also seems to be a lot easier for me to read...
我喜欢在“列”中排列代码/初始化......证明在使用“列”模式编辑器进行编辑时非常有用,而且对我来说似乎更容易阅读......
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true }, // Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000, false }, // Comment 3
{NULL, 5, true }, // Comment 4
};
In contrast, the same code not indented and formatted as above would appear... (A little harder to read to my eyes)
相比之下,未缩进和格式化的相同代码会出现......(对我的眼睛来说有点难以阅读)
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true},// Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000,false}, // Comment 3
{NULL, 5, true }, // Comment 4
};
回答by Sebastian Mach
Public Top - Private Down
公共上衣 - 私人羽绒
A seemingly small optimization, but ever since I switched to this convention, I have a way more fun time to grasp my classes, especially after I haven't looked at them for 42 years.
一个看似很小的优化,但是自从我切换到这个约定之后,我有更多有趣的时间来掌握我的课程,尤其是在我已经 42 年没有看过它们之后。
Having a consistent member visibility, going from points of frequent interest down to the boring stuff, is extremely helpful, especially when the code ought to be self-documenting.
拥有一致的成员可见性,从经常感兴趣的点到无聊的东西,是非常有帮助的,尤其是当代码应该是自我记录的时候。
(sidenote for qt-users: slots come before signals because they should be callable like non-slot member functions, and apart from their slottyness be indistinguishable from non-slots)
(qt 用户的旁注:插槽在信号之前,因为它们应该像非插槽成员函数一样可调用,并且除了它们的插槽性之外,与非插槽无法区分)
- Public, protected, private
- then Factory, ctor, dtor, copying, swapping
- then the class' Interface
At the very last, in a seperate
private:
section, comes the data (ideally only an impl-pointer).
- 公共的、受保护的、私有的
- 然后工厂,ctor,dtor,复制,交换
- 然后是类的接口 最后,在一个单独的
private:
部分,是数据(理想情况下只有一个 impl 指针)。
This rule also helps a ton if you have problems keeping your class declaration uncluttered.
如果您在保持类声明整洁时遇到问题,此规则也有很大帮助。
class Widget : public Purple {
public:
// Factory methods.
Widget FromRadians (float);
Widget FromDegrees (float);
// Ctors, rule of three, swap
Widget();
Widget (Widget const&);
Widget &operator = (Widget const &);
void swap (Widget &) throw();
// Member methods.
float area() const;
// in case of qt {{
public slots:
void invalidateBlackHole();
signals:
void areaChanged (float);
// }}
protected:
// same as public, but for protected members
private:
// same as public, but for private members
private:
// data
float widgetness_;
bool isMale_;
};
回答by zerbp
In if
statements, when there are difficult conditions, you can clearly show which level each condition is using indentation.
在if
语句中,当遇到困难条件时,可以清楚地显示每个条件使用缩进的级别。
if ( ( (var1A == var2A)
|| (var1B == var2B))
&& ( (var1C == var2C)
|| (var1D == var2D)))
{
// do something
}
回答by seh
Compile-time polymorphism
编译时多态
(Also known as syntactic polymorphism and static polymorphism, contrast with runtime polymorphism.)
(也称为句法多态和静态多态,与运行时多态形成对比。)
With template functions, one can write code that relies on type constructors and call signatures of families of parametrized types, without having to introduce a common base class.
使用模板函数,人们可以编写依赖于参数化类型系列的类型构造函数和调用签名的代码,而无需引入公共基类。
In the book Elements of Programming, the authors refer to this treatment of types as abstract genera. With conceptsone can specify the requirements on such type parameters, though C++ doesn't mandate such specifications.
在Elements of Programming一书中,作者将这种类型处理称为抽象类。使用概念可以指定对此类类型参数的要求,尽管 C++ 不强制要求此类规范。
Two simple examples:
两个简单的例子:
#include <stdexcept>
template <typename T>
T twice(T n) {
return 2 * n;
}
InIt find(InIt f, InIt l,
typename std::iterator_traits<InIt>::reference v)
{
while (f != l && *f != v)
++f;
return f;
}
int main(int argc, char* argv[]) {
if (6 != twice(3))
throw std::logic_error("3 x 2 = 6");
int const nums[] = { 1, 2, 3 };
if (nums + 4 != find(nums, nums + 4, 42))
throw std::logic_error("42 should not have been found.");
return 0;
}
One can call twice
with any regular type that has a binary *
operator defined. Similarly, one can call find()
with any types that are comparable and that model Input Iterator. One set of code operates similarly on different types, with no shared base classes in sight.
可以twice
使用*
定义了二元运算符的任何常规类型进行调用。类似地,可以find()
使用任何可比较的类型和模型Input Iterator调用。一组代码对不同类型的操作类似,看不到共享基类。
Of course, what's really going on here is that it's the same source code being expandedinto various type-specific functions at template instantiation time, each with separate generated machine code. Accommodating the same set of types without templates would have required either 1) separate hand-written functions with specific signatures, or 2) runtime polymorphism through virtual functions.
当然,这里真正发生的是在模板实例化时将相同的源代码扩展为各种特定于类型的函数,每个函数都有单独的生成机器代码。在没有模板的情况下容纳相同的类型集将需要 1) 单独的具有特定签名的手写函数,或 2) 通过虚函数实现运行时多态性。
回答by ididak
No favorites but I willfix code that has:
没有收藏夹,但我会修复具有以下内容的代码:
- tabs - causes misalignment in many IDEs and code review tools, because they don't always agree on tab at mod 8 spaces.
- lines longer than 80 columns - let's face it, shorter lines are more readable. My brain can parse most coding conventions, as long as the lines are short.
- lines with trailing whitespaces - git will complain about it as whitespace errors, which show up as red blobs in diffs, which is annoying.
- 制表符 - 在许多 IDE 和代码工具中导致错位,因为它们并不总是在 mod 8 空格上就制表符达成一致。
- 超过 80 列的行 - 让我们面对现实,较短的行更具可读性。只要行短,我的大脑就可以解析大多数编码约定。
- 带有尾随空格的行 - git 会将其抱怨为空格错误,在差异中显示为红色斑点,这很烦人。
Here is a one-liner to find the offending files:
这是查找违规文件的单行代码:
git grep -I -E '<tab>|.{81,}| *$' | cut -f1 -d: | sort -u
where <tab>
is the tab character (POSIX regexp doesn't do \t)
<tab>
制表符在哪里(POSIX regexp 不做 \t)