你最喜欢的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 ] ) { ; }
jalf.. 65
RAII可能是最重要的习语.我们认为资源应该映射到对象,以便根据声明这些对象的范围自动管理它们的生命周期.
例如,如果在堆栈上声明了文件句柄,那么一旦我们从函数返回(或循环,或者在其中声明的范围),它应该被隐式关闭.如果动态内存分配被分配为类的成员,则应该在销毁该类实例时隐式释放它.等等.每种资源内存分配,文件句柄,数据库连接,套接字以及必须获取和释放的任何其他类型的资源都应该包含在这样的RAII类中,其生命周期取决于它的范围.声明.
这样做的一个主要优点是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. }
无论我们如何离开函数,以及文件打开后发生的事情,我们都不需要显式关闭文件,或者在该函数中处理异常(例如try-finally).相反,文件被清理,因为它绑定到一个本地对象,当它超出范围时会被销毁.
RAII也不太常见,称为SBRM(范围限制资源管理).
也可以看看:
ScopeGuard允许代码"在抛出异常时自动调用'撤消'操作.."
kshahar.. 59
创建枚举时,将它们放在命名空间中,以便您可以使用有意义的名称访问它们:
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
名称的范围内.
上一个示例使用作用域枚举(也称为强类型枚举)重写:
enum class EntityType { Ground = 0, Human, Aerial, Total }; void foo(EntityType entityType) { if (entityType == EntityType::Ground) { /*code*/ } }
使用作用域枚举还有其他显着的好处:缺少隐式转换,可能的前向声明以及使用自定义基础类型(不是默认值int
)的能力.
RAII可能是最重要的习语.我们认为资源应该映射到对象,以便根据声明这些对象的范围自动管理它们的生命周期.
例如,如果在堆栈上声明了文件句柄,那么一旦我们从函数返回(或循环,或者在其中声明的范围),它应该被隐式关闭.如果动态内存分配被分配为类的成员,则应该在销毁该类实例时隐式释放它.等等.每种资源内存分配,文件句柄,数据库连接,套接字以及必须获取和释放的任何其他类型的资源都应该包含在这样的RAII类中,其生命周期取决于它的范围.声明.
这样做的一个主要优点是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. }
无论我们如何离开函数,以及文件打开后发生的事情,我们都不需要显式关闭文件,或者在该函数中处理异常(例如try-finally).相反,文件被清理,因为它绑定到一个本地对象,当它超出范围时会被销毁.
RAII也不太常见,称为SBRM(范围限制资源管理).
也可以看看:
ScopeGuard允许代码"在抛出异常时自动调用'撤消'操作.."
创建枚举时,将它们放在命名空间中,以便您可以使用有意义的名称访问它们:
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
名称的范围内.
上一个示例使用作用域枚举(也称为强类型枚举)重写:
enum class EntityType { Ground = 0, Human, Aerial, Total }; void foo(EntityType entityType) { if (entityType == EntityType::Ground) { /*code*/ } }
使用作用域枚举还有其他显着的好处:缺少隐式转换,可能的前向声明以及使用自定义基础类型(不是默认值int
)的能力.
复制交换习惯用法提供了异常安全的复制.它要求实现正确的复制ctor和swap.
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(Argument Dependent Lookup)实现swap方法.
这个成语很重要,因为它处理自我赋值[1],强烈的异常保证[2],并且通常很容易编写.
[1]即使自我分配没有尽可能有效地处理,它应该是罕见的,所以如果它永远不会发生,这实际上更快.
[2]如果抛出任何异常,*this
则不修改object()的状态.
将类作为模板参数传递给其基类时会发生CRTP:
templatestruct BaseCRTP {}; struct Example : BaseCRTP {};
在基类中,只需通过强制转换(static_cast或dynamic_cast工作),它就可以得到派生实例的ahold,完成派生类型:
templatestruct BaseCRTP { void call_foo() { Derived& self = *static_cast (this); self.foo(); } }; struct Example : BaseCRTP { void foo() { cout << "foo()\n"; } };
实际上,call_foo已经注入到派生类中,可以完全访问派生类的成员.
随意编辑和添加特定的使用示例,可能是其他SO帖子.
pImpl习惯用法是将类的接口与其实现分离的非常有用的方法.
通常,类定义必须包含成员变量和方法,这可能会暴露太多信息.例如,成员变量可以是标题中定义的类型,我们不希望包含在任何地方.
该windows.h
标题是这里最好的例子.我们可能希望HANDLE
在一个类中包含一个或另一个Win32类型,但是我们不能HANDLE
在类定义中放入一个而不必包含windows.h
所使用的类的所有地方.
然后解决方案是创建类的P rivate IMPL ementation或P ointer-to- IMPL ementation,并让public实现只存储指向私有的指针,并转发所有成员方法.
例如:
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
现在,它的实现与其公共接口分离,因此
它可以使用其他头文件中的成员和类型,而不需要在使用类时存在这些依赖关系,并且
可以在不强制重新编译使用该类的代码的情况下修改实现.
该类的用户只需包含标题,该标题不包含有关该类实现的任何特定内容.所有实现细节都包含在里面foo.cpp
.
我喜欢在'列'中排列代码/初始化...在使用'column'模式编辑器进行编辑时证明非常有用,而且对我来说似乎也更容易阅读...
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 };
Public Top - Private Down
这是一个看似很小的优化,但自从我改用这个大会以来,我有更多的时间来掌握我的课程,特别是在我42年没有看过它们之后.
拥有一致的成员可见性,从经常感兴趣的点到无聊的东西,是非常有帮助的,特别是当代码应该是自我记录时.
(qt-users的旁注:插槽位于信号之前,因为它们应该像非插槽成员函数一样可调用,除了它们的插槽与非插槽无法区分)
公共,受保护,私人
然后工厂,ctor,dtor,复制,交换
然后是类的接口最后,在一个单独的private:
部分,来自数据(理想情况下只是一个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列的行 - 让我们面对它,更短的行更具可读性.只要线条很短,我的大脑就可以解析大多数编码约定.
带有尾随空格的行 - git会抱怨它为空格错误,它在差异中显示为红色斑点,这很烦人.
这是一个单行找到有问题的文件:
git grep -I -E '|.{81,}| *$' | cut -f1 -d: | sort -u
tab字符在哪里(POSIX regexp不做\ t)
re:ididak
我修复了将长语句分成太多短语的代码.
让我们面对现实:不再是90年代了.如果你的公司买不起宽屏液晶显示器,你需要得到更好的工作:)
(也称为句法多态和静态多态,与运行时多态性形成对比.)
使用模板函数,可以编写依赖于类型构造函数和调用参数化类型族的签名的代码,而无需引入公共基类.
在" 编程元素 "一书中,作者将这种类型的处理称为抽象属.使用概念,可以指定对此类型参数的要求,但C++不要求这样的规范.
两个简单的例子:
#includetemplate T twice(T n) { return 2 * n; } InIt find(InIt f, InIt l, typename std::iterator_traits ::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; }
可以twice
使用任何*
定义了二元运算符的常规类型调用.类似地,可以调用find()
任何可比较的类型和输入迭代器模型.一组代码在不同类型上操作类似,没有共享基类.
当然,这里真正发生的是在模板实例化时将相同的源代码扩展为各种类型特定的函数,每个函数都有单独生成的机器代码.容纳不带模板的相同类型的集合将需要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在他的" 面向对象软件开发的设计模式"一书中描述.
if (expression) // preferred - if keyword sticks out more
与
if(expression) // looks too much like a void function call
我想这暗示着我喜欢我的函数调用没有空格分隔符
foo(parm1, parm2);