什么是你最喜欢的C ++编码风格成语
你最喜欢的C ++编码风格成语是什么? 我正在问风格或编码排版,比如放大括号的地方,关键字后面是否有空格,缩进的大小等。这与最佳做法或要求相反,例如总是使用delete[]
删除数组。
下面是我最喜欢的一个例子:在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 ] ) { ; }
创build枚举时,将它们放在一个名称空间中,以便可以用一个有意义的名称访问它们:
namespace EntityType { enum Enum { Ground = 0, Human, Aerial, Total }; } void foo(EntityType::Enum entityType) { if (entityType == EntityType::Ground) { /*code*/ } }
编辑 :但是,这种技术已经成为过时的C + + 11。 应该使用范围枚举 (用enum class
或enum struct
):它更安全,简洁,更灵活。 使用旧式枚举值将被放置在外部范围中。 使用新式枚举,它们被放置在enum class
名的范围内。
前面的示例使用作用域枚举重写(也称为强types枚举 ):
enum class EntityType { Ground = 0, Human, Aerial, Total }; void foo(EntityType entityType) { if (entityType == EntityType::Ground) { /*code*/ } }
使用范围枚举还有其他一些重要的好处:没有隐式转换,可能的前向声明,以及使用自定义底层types(而不是默认的int
)的能力。
RAII:资源获取是初始化
RAII可能是最重要的成语。 资源应该被映射到对象是这样的想法,所以它们的生命周期是根据这些对象被声明的范围自动pipe理的。
例如,如果文件句柄是在堆栈中声明的,那么一旦我们从函数(或者循环,或者其中声明的范围)返回,它就应该隐式closures。 如果dynamic内存分配是作为类的成员进行分配的,那么当该类实例被销毁时,它应该被隐式释放。 等等。 每一种资源内存分配,文件句柄,数据库连接,套接字以及任何其他需要获取和释放的资源都应该包装在这样一个RAII类中,它的生命周期取决于它的范围声明。
这样做的一个主要优点是C ++保证当对象超出范围时调用析构函数, 而不pipe控制权是如何离开范围的 。 即使抛出一个exception,所有的本地对象都将超出范围,所以它们的相关资源将被清除。
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. }
无论我们如何离开函数,以及打开文件后发生了什么,我们都不需要显式closures文件,也不需要在该函数中处理exception(例如try-finally)。 相反,文件被清理,因为它被绑定到一个本地对象,当它超出范围时被破坏。
RAII也不太知名,叫做SBRM(Scope-Bound Resource Management)。
也可以看看:
- ScopeGuard允许代码“在抛出exception的情况下自动调用”撤消“操作。
复制交换
复制交换成语提供exception安全的复制。 它要求执行正确的拷贝和交换。
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); }
您也可以直接使用ADL(参数相关查找)实现交换方法。
这个习语很重要,因为它处理自我赋值[1] ,做出强有力的例外保证[2] ,而且往往很容易写。
[1]虽然自我分配不是尽可能有效地处理,但它应该是罕见的 ,所以如果从来没有发生,这实际上更快。
[2]如果抛出任何exception,则对象( *this
)的状态不会被修改。
CRTP:好奇地重复模板模式
当您将一个类作为模板parameter passing给其基类时,会发生CRTP :
template<class Derived> struct BaseCRTP {}; struct Example : BaseCRTP<Example> {};
在基类中,它可以通过简单的cast( static_cast或者dynamic_cast work)来获得派生实例的inheritance, 并且派生types完整 :
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"; } };
实际上, call_foo已被注入到派生类中,并且可以完全访问派生类的成员。
随意编辑和添加使用的具体例子,可能到其他SO职位 。
pImpl:指针实现
pImpl习惯用法是将类的接口与其实现分离的一种非常有用的方法。
通常,类定义必须包含成员variables以及方法,这可能会暴露过多的信息。 例如,一个成员variables可能是头部定义的types,我们不希望在任何地方都包含这个头部。
windows.h
头文件是一个很好的例子。 我们可能希望在一个类中包装一个HANDLE
或者另一个Win32types,但是我们不能在类定义中放置一个HANDLE
,而不必在任何地方使用windows.h
。
然后,解决scheme是创build一个私有的IMPL ementation或P ointer-to- IMPL ementation类,让公共实现只存储一个指向私有的指针,并转发所有的成员方法。
例如:
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(); }
现在foo
的实现已经从它的公共接口中分离出来了
- 它可以使用其他头文件中的成员和types,而不需要这些相关性在类被使用时出现
- 可以在不强制重新编译使用类的代码的情况下修改实现。
这个类的用户只是简单的包含头文件,其中没有包含任何具体的类的实现。 所有的实现细节都包含在foo.cpp
。
我喜欢在“列”中排列代码/初始化…当使用“列”模式编辑器进行编辑时,certificate非常有用,而且似乎对我来说更容易阅读…
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 };
相比之下,相同的代码不会像上面那样缩进和格式化…(稍微难以阅读我的眼睛)
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 };
公共顶部 – 私人羽绒服
一个看起来很小的优化,但是自从我转换到这个习惯之后,我有更多的时间来抓我的课程,特别是在我没有看了他们42年之后。
具有一致的成员可见性,从频繁的兴趣点到无聊的东西,是非常有用的,尤其是当代码应该自我logging。
(qt-users的旁注:插槽在信号之前,因为它们应该像非插槽成员函数一样可调用,并且除了它们的插槽之外,它们与非插槽没有区别)
- 公共,保护,私人
- 然后工厂,Ctor,Dtor,复制,交换
- 那么类的接口最后,在一个单独的
private:
section中,传入数据(理想情况下只有一个impl指针)。
如果你在保持你的类声明不整齐的时候遇到问题,这个规则也有帮助。
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_; };
在if
语句中,当条件有困难时,可以清楚地显示每个条件使用缩进的级别。
if ( ( (var1A == var2A) || (var1B == var2B)) && ( (var1C == var2C) || (var1D == var2D))) { // do something }
没有最爱,但我会修复代码:
- 选项卡 – 导致许多IDE和代码审查工具不匹配,因为它们并不总是在mod 8空格的选项卡上达成一致。
- 比80列更长的行 – 让我们面对它,更短的行更可读。 只要行数很短,我的大脑就可以parsing大部分编码规则。
- 行尾有空白 – git会抱怨它是空格错误 ,在diff中显示为红色斑点,这很烦人。
这里是find有问题的文件的一行代码:
git grep -I -E '<tab>|.{81,}| *$' | cut -f1 -d: | sort -u
其中<tab>
是制表符(POSIX正则expression式不是\ t)
重新:ididak
我修复了将冗长的语句分解成太多短的代码。
让我们面对现实:这不是90年代。 如果你的公司买不起宽屏液晶显示器的编码器,你需要得到更好的工作:)
编译时多态性
(也称为语法多态和静态多态,与运行时多态对比)
使用模板函数,可以编写依赖于types构造函数的代码,并调用参数化types的系列的签名,而不必引入公共基类。
在“ 编程元素 ”一书中,作者将这种types的处理称为抽象属 。 有了概念,人们可以指定这些types参数的要求,尽pipeC ++没有强制规定这些规范。
两个简单的例子:
#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; }
可以使用定义了二元运算符的常规types调用twice
。 同样,可以使用任何可比较的types和模型Input Iterator调用find()
。 一组代码在不同types上运行类似,没有共享基类。
当然,这里实际上是在模板实例化时将相同的源代码扩展为各种types特定的函数,每个函数都有独立的生成的机器代码。 在没有模板的情况下容纳相同types的集合将需要1)具有特定签名的单独的手写函数,或2)通过虚拟函数的运行时多态性。
模板和钩子
这是在框架中尽可能多的处理方式,并为框架用户定制门户或钩子 。 也被称为热点和模板方法 。
class Class { void PrintInvoice(); // Called Template (boilerplate) which uses CalcRate() virtual void CalcRate() = 0; // Called Hook } class SubClass : public Class { virtual void CalcRate(); // Customized method }
Wolfgang Pree在其着作“面向对象软件开发的devise模式”一书中进行了描述。
我不知道它是否符合成语的要求,但是相当多的重型模板编程依赖于SFINAE(通常很重要)(replace失败不是错误)。 上一个问题的答案有几个例子。
在与一个部分失明的人工作之后,他应他的要求,转而使用更多的空间。 我当时不喜欢它,但现在我更喜欢它。 在我的头顶,标识符和关键字之间没有空格的地方,以及函数名称后面和下面的括号之前的地方。
void foo( int a, int b ) { int c = a + ( a * ( a * b ) ); if ( c > 12 ) c += 9; return foo( 2, c ); }
if / while /用于带有空格分隔符的括号expression式
if (expression) // preferred - if keyword sticks out more
与
if(expression) // looks too much like a void function call
我想这意味着我喜欢我的函数调用没有空格分隔符
foo(parm1, parm2);
我真的很喜欢在if的同一行上写一个小的声明
int myFunc(int x) { if(x >20) return -1; //do other stuff .... }
不知道这是否算作一个成语,但我倾向于使用doxygen样式的内嵌注释,即使项目不是 – 使用doxygen …
bool MyObjects::isUpToSomething() ///< Is my object up to something
(旁白,我的评论通常不是那么蹩脚)。
把函数名放在一个新行上是很有用的,所以你可以像grep一样
grep -R '^fun_name' .
为他们。 我已经看到了用于大量GNU项目的样式,喜欢它:
static void fun_name (int a, int b) { /* ... */ }
将每个方法或函数参数写在一个单独的行上,以便可以轻松地进行评论。
int ReturnMaxValue( int* inputList, /* the list of integer values from which to get the maximum */ long size, /* count of the number of integer values in inputList */ char* extraArgs /* additional arguments that a caller can provide. */ )
我build议PIMPL或James Coplien最初称之为“Handle Body”。
这个习惯用法可以让界面与执行完全分离。 在重新编写和重新发布一个主要的CORBA中间件组件时,这个习惯用来将API从实现中彻底解耦。
这实际上消除了任何可能的逆向工程。
James Coplien出色的书“ 高级C ++编程风格和习惯”是一个很好的C ++习语资源。 强烈推荐!
编辑:正如Neil在下面指出的那样,这本书已经过时了,他的许多build议实际上都被纳入了C ++标准本身。 不过,我仍然觉得它是有用信息的来源,尤其是, 在他的PLoP论文的C ++习惯用法中 ,许多习语被重写成pattermforms。
我总是挑选并编辑以下内容:
- 多余的换行符
- 在EOF没有换行符
我通常坚持* BSD STYLE(9)中描述的KNF
logging函数行的返回值,所以很容易find。
int function(void) /* return 1 on success, 0 on failure */ { return 1; };
我倾向于把我的所有ifs。
if (condition) { complicated code goes here } else { /* This is a comment as to why the else path isn't significant */ }
尽pipe它惹恼了我的同事。 你可以一目了然地看到,在编码过程中,我考虑了其他情况。