你最喜欢的C ++ Coding Style成语是什么?

时间:2008-11-09 17:35:09

标签: c++ coding-style

你最喜欢的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 ] )
{
    ;
}

24 个答案:

答案 0 :(得分:63)

RAII:资源获取是初始化

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允许代码“在抛出异常的情况下自动调用'撤消'操作..”

答案 1 :(得分:57)

创建枚举时,将它们放在命名空间中,以便您可以使用有意义的名称访问它们:

namespace EntityType {
    enum Enum {
        Ground = 0,
        Human,
        Aerial,
        Total
    };
}

void foo(EntityType::Enum entityType)
{
    if (entityType == EntityType::Ground) {
        /*code*/
    }
}

编辑:但是,这种技术在C ++ 11中已经过时了。应该使用 Scoped枚举(用enum classenum struct声明):它更加类型安全,简洁,灵活。对于旧式枚举,值将放置在外部范围中。使用新式枚举,它们被置于enum class名称的范围内 上一个示例使用作用域枚举(也称为强类型枚举)重写:

enum class EntityType {
    Ground = 0,
    Human,
    Aerial,
    Total
};

void foo(EntityType entityType)
{
    if (entityType == EntityType::Ground) {
        /*code*/
    }
}

使用作用域枚举还有其他显着的好处:缺少隐式转换,可能的前向声明以及使用自定义基础类型的能力(不是默认的int)。

答案 2 :(得分:37)

复制交换

copy-swap习惯用法提供异常安全的复制。它要求实现正确的复制ctor和交换。

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)directly实现交换方法。

这个成语很重要,因为它处理自我赋值 [1] ,使得强大的异常保证 [2] ,并且通常很容易编写。


[1] 即使自我分配没有尽可能有效地处理,但它应该是稀有,所以如果它永远不会发生,这实际上更快

[2] 如果抛出任何异常,则不会修改对象(*this)的状态。

答案 3 :(得分:34)

CRTP:奇怪的重复模板模式

将类作为模板参数传递给其基类时会发生

CRTP

template<class Derived>
struct BaseCRTP {};

struct Example : BaseCRTP<Example> {};

在基类中,只需通过强制转换( static_cast dynamic_cast 完成派生类型 em> work):

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 注入到派生类中,可以完全访问派生类的成员。

可以随意编辑和添加特定的使用示例,可​​能是other SO posts

答案 4 :(得分:27)

pImpl:指向实现的指针

pImpl习惯用法是将类的接口与其实现分离的非常有用的方法。

通常,类定义必须包含成员变量和方法,这可能会暴露太多信息。例如,成员变量可以是标题中定义的类型,我们不希望包含在任何地方。

windows.h标题是这里的一个主要示例。我们可能希望在类中包含HANDLE或另一个Win32类型,但是我们不能在类定义中放置HANDLE而不必在使用类的任何地方包含windows.h

然后解决方案是创建 P rivate IMPL 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的实现现在与其公共接口分离,因此

  • 它可以使用其他标题中的成员和类型,而不需要在使用该类时存在这些依赖项,并且
  • 可以在不强制重新编译使用该类的代码的情况下修改实现。

该类的用户只需包含标题,其中不包含任何有关该类实现的具体信息。所有实现细节都包含在foo.cpp

答案 5 :(得分:22)

我喜欢在“列”中排列代码/初始化...在使用具有“列”模式功能的编辑器编辑时证明非常有用,而且对我来说似乎也更容易阅读...

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
};

答案 6 :(得分:14)

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_;
};

答案 7 :(得分:11)

if语句中,当条件有困难时,您可以清楚地显示每个条件使用缩进的级别。

if (  (  (var1A == var2A)
      || (var1B == var2B))
   && (  (var1C == var2C)
      || (var1D == var2D)))
{
   // do something
}

答案 8 :(得分:7)

没有收藏夹,但我修复具有以下内容的代码:

  1. 标签 - 在许多IDE和代码审查工具中导致错位,因为它们并不总是在mod 8空格的标签上达成一致。
  2. 超过80列的行 - 让我们面对它,更短的行更具可读性。只要线条很短,我的大脑就可以解析大多数编码惯例。
  3. 带有尾随空格的行 - git会将其作为空白错误进行投诉,它们在差异中显示为红色斑点,这很烦人。
  4. 以下是查找有问题文件的单行程序:

    git grep -I -E '<tab>|.{81,}|  *$' | cut -f1 -d: | sort -u
    

    其中<tab>是制表符(POSIX regexp不做\ t)

答案 9 :(得分:7)

re:ididak

我修复了将长语句分成太多短语的代码。

让我们面对现实:不再是90年代了。 如果贵公司无法为其编码器提供宽屏液晶显示器,则需要做得更好:)

答案 10 :(得分:7)

编译时多态性

(也称为句法多态和静态多态,与运行时多态性形成对比。)

使用模板函数,可以编写依赖于类型构造函数的代码,并调用参数化类型族的签名,而无需引入公共基类。

在书Elements of Programming中,作者将这种类型的处理称为抽象属。使用 concepts ,可以指定对此类型参数的要求,但C ++不要求这样的规范。

两个简单的例子:

#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;
}

可以使用定义了二进制twice运算符的任何常规类型调用*。同样,可以使用任何可比较的类型和输入迭代器模型调用find()。一组代码在不同类型上操作类似,没有共享基类。

当然,这里真正发生的是,在模板实例化时,它是扩展到各种类型特定函数的相同源代码,每个源代码都有单独生成的机器代码。容纳不带模板的同一组类型将需要1)具有特定签名的单独手写函数,或2)通过虚函数的运行时多态性。

答案 11 :(得分:5)

if / while /表示带空格分隔符的括号表达式

if (expression)  // preferred - if keyword sticks out more

VS

if(expression)  // looks too much like a void function call

我想这意味着我喜欢我的函数调用没有空格分隔符

foo(parm1, parm2);

答案 12 :(得分:5)

模板和挂钩

这是一种在框架中尽可能多地处理并提供 hook 以供框架用户自定义的方法。也称为热点和Template Method

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在他的书Design Patterns for Object-Oriented Software Development中描述。

答案 13 :(得分:4)

在与部分失明的人合作之后 - 并应他的要求 - 我转而使用更多的空间。我当时不喜欢它,但现在我更喜欢它。在我的脑海中,唯一一个标识符和关键字之间没有空格的地方,以及函数名之后和下面的括号之前。

void foo( int a, int b )
{
  int c = a + ( a * ( a * b ) );
  if ( c > 12 )
    c += 9;
  return foo( 2, c );
}

答案 14 :(得分:4)

我不知道它是否有资格成为一个成语,但是相当多的重型模板编程依赖于(通常很多)SFINAE(替换失败不是错误)。 a previous question的几个答案都有例子。

答案 15 :(得分:4)

我真的很喜欢在与if

相同的行上添加一个小语句
int myFunc(int x) {
   if(x >20) return -1;
   //do other stuff ....
}

答案 16 :(得分:3)

不确定这是否算作成语,但我倾向于使用doxygen - 样式内联注释,即使项目不是-yet-使用doxygen ......

bool MyObjects::isUpToSomething() ///< Is my object up to something 

(除此之外。我的评论通常不那么蹩脚。)

答案 17 :(得分:2)

将函数名称放在新行上很有用,所以你可以像

一样
grep -R '^fun_name' .

对他们来说。我已经看到用于大量GNU项目的样式,并且喜欢它:

static void
fun_name (int a, int b) {
    /* ... */
}

答案 18 :(得分:2)

记录功能线上的返回值,因此很容易找到它们。

int function(void) /* return 1 on success, 0 on failure */ 
{
    return 1;
};

答案 19 :(得分:1)

我建议使用PIMPL或James Coplien最初称之为“Handle Body”。

这个成语允许您完全将接口与实现分离。在处理主要CORBA中间件组件的重写和重新发布时,这个习惯用于完全将API与实现分离。

这实际上消除了逆向​​工程的任何可能性。

C ++成语的优秀资源是James Coplien出色的书“Advanced C++ Programming Styles and Idioms”。强烈推荐!

编辑如下面Neil所指出的,这本书已经过时了,他的许多建议实际上都被纳入了C ++标准本身。但是,我仍然觉得它是有用信息的来源,尤其是。以PLoP paper on C++ idioms的形式,其中许多成语被改写成模式。

答案 20 :(得分:1)

将每个方法或函数参数写在一个单独的行上,以便可以轻松地进行注释。

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.    */
)

答案 21 :(得分:0)

我通常坚持使用* BSD STYLE(9)

中描述的KNF

答案 22 :(得分:0)

我总是挑剔并编辑以下内容:

  • 多余的新行
  • EOF没有新行

答案 23 :(得分:-1)

我倾向于在我的所有ifs上添加其他内容。

if (condition)
{
    complicated code goes here
}
else
{
    /* This is a comment as to why the else path isn't significant */ 
}

即使它让我的同事烦恼。 你可以一眼就看出,我在编码过程中考虑了其他情况。