C++ Dos and Don'ts

最小化头文件

  • 不要包含不必要的头文件

   尽量使用前向声明的方式,目的是为了减少编译时间What are forward declarations in C++?,并且在头文件发生改变的时候,减少重新编译的文件。

  • 将内部类移动到实现中
// 内部类的声明
class Whatever {
 public:
  /* ... */
 private:
  struct DataStruct;
  std::vector<DataStruct> data_;
};

struct Whatever::DataStruct {
};

目的就是为了减少头文件的大小,以及导致变化的因素,减少编译的时间

某些时候你不能这么做,因为某些STL数据结构,需要在声明的时候就知道其定义,比如:std::deque

  • 将静态实现类放到匿名空间中
#include "BigImplementationDetail.h"
class PublicInterface {
  public:
   /* ... */
  private:
   static BigImplementationDetail detail_;
};
// 你应该将BigImplementationDetail挪到实现文件中,并放置在匿名空间中
namespace {
BigImplementationDetail g_detail;
}  // namespace

​   不使用这个接口的人是不需要关心BigImplementationDetail实现细节的。使用匿名空间的好处就是可以限制对象的作用域在本文件中。

Reference
1. Why are unnamed namespaces used and what are their benefits?
2. Why is an unnamed namespace used instead of static?

停止头文件中的内联

  • 一些get,set之类的简单操作是可以内联的,记住在头文件中进行定义的时候都会隐式进行内联
  • 停止对一些复杂方法进行内联
class DontDoThis {
 public:
  int ComputeSomething() {
    int sum =0;
    for (int i = 0; i < limit; ++i) {
      sum += OtherMethod(i, ... );
    }
    return sum;
  }
};
  • 停止对虚方法进行内联

   在大多数情况不要对一个虚方法进行内联,即使是因为这些方法很短也是不可以的,编译器必须在运行时根据虚函数表和指向的类型做分发,如果内联了就无法知道指向的类型。

Reference
1. Can virtual functions be inlined?
2. Are inline virtual functions really a non-sense?

  • 停止内联构造和析构函数

   构造函数和析构函数要比我们想象中的要复杂,因为编译器会帮我们插入很多初始化的代码,因此不要认为构造函数和析构函数中没写代码就可以内联。

  • 什么情况下可以有构造函数和析构函数

   如果你的类只有POD类型的数据,并且没有显示的声明析构函数,那么编译器也不会生成trivial destructor。

struct Data {
  Data() : count_one(0), count_two(0) {}
  // No explicit destructor, thus no implicit destructor either.

  // The members must all be POD for this trick to work.
  int count_one;
  int count_two;
};

​   没有继承,只有很少一些POD类型,构造函数都是一些trivial的整型操作,所以是可以内联的。对于抽象类, 并且没有成员的情况,它是可以安全内联一个trivial的析构函数

class Interface {
 public:
  virtual ~Interface() {}

  virtual void DoSomething(int parameter) = 0;
  virtual int GetAValue() = 0; 
};

下面两个接口,不能进行inline。

class ClaimsToBeAnInterface : public base:RefCounted<ClaimsToBeAnInterface> {
 public:
  virtual ~ClaimsToBeAnInterface() { /* But derives from a template! */ }
};

class HasARealMember {
 public:
  virtual void InterfaceMethod() = 0;
  virtual ~HasARealMember() {}

 protected:
  vector<string> some_data_;
};
  • 小心你的访问器

不是所有的访问器都是轻量级的

class Foo {
 public:
  int count() const { return count_; }

 private:
  int count_;
};

   上面这个访问器是trivial的,可以很安全的inline,但是下面这些代码就不行了,即使它们看起来很像。

struct MyData {
  vector<GURL> urls_;
  base::Time last_access_;
};

class Manager {
 public:
  MyData get_data() { return my_data_; }

 private:
  MyData my_data_;
};

MyData底层的数据拷贝很复杂,所以不适合inline

静态变量

   动态初始化函数作用域内的静态变量在C++11中是线程安全的,因此base::LazyInstance被广泛使用,

void foo() {
    static int OK_COUNT = ComputeTheCount();  // OK now, previously a problem.
    static int GOOD_COUNT = 42;  // C++03 3.6.2 says this is done before dynamic initialization, so probably thread-safe.
    static constexpr int BETTER_COUNT = 42;  // Even better, as this will now likely be inlined at compile time.
}

变量初始化

在C++11中有很多方式可以用来初始化一个变量,优先遵从下面这些规则:

  • 对于简单变量的初始化,以及多个literal value组成一个对象的初始化使用赋值语法
int i = 1;
std::string s = "Hello";
std::pair<bool, double> p = {true, 2.0};
std::vector<std::string> v = {"one", "two", "three"};

   这里使用=号并不会没有()效率高,因为这里编译器不会生成临时变量进行拷贝,并且确保只有隐式的构造函数被调用,因此读者在看到这种调用方式可以假设没有什么复杂或微妙的事情发生。

  • 当构造函数要执行某些重要逻辑的时候,使用构造语法,使用一个显示的构造函数。
MyClass c(1.7, false, "test");
std::vector<double> v(500, 0.97);  // Creates 500 copies of the provided initializer
  • 不属于上面情况的时候使用C++11的统一初始化语法
class C {
 public:
  explicit C(bool b) { ... };
  ...
};
class UsesC {
  ...
 private:
  C c{true};  // Cannot use '=' since C() is explicit (and "()" is invalid syntax here)
};

class Vexing {
 public:
  explicit Vexing(const std::string& s) { ... };
  ...
};
void func() {
  Vexing v{std::string()};  // Using "()" here triggers "most vexing parse";
                            // "{}" is arguably more readable than "(())"
  ...
  • 不要将统一初始化结合auto使用
auto x{1};  // Until C++17, decltype(x) is std::initializer_list<int>, not int!

优先使用MakeUnique 替换WrapUnique

   base::MakeUnique<Type>(…)base::WrapeUnique(new Type())是等同的,MakeUnique更好,因为通常来说用它很难写出不安全的代码。

 return std::unique_ptr<C>(new C(1, 2, 3));  // BAD: type name mentioned twice
 return base::WrapUnique(new C(1, 2, 3));    // BAD: bare call to new
 return base::MakeUnique<C>(1, 2, 3);        // GOOD

注意: MakeUnique就是C++14中的make_unique的实现

  • 不要将MakeUnique设置为类的友元,因为这会让任何人都可以构造这个类
class Bad {
 public:
  std::unique_ptr<Bad> Create() { return base::MakeUnique<Bad>(); }
  // ...
 private:
  Bad();
  // ...
  friend std::unique_ptr<Bad> base::MakeUnique<Bad>();  // Lost access control
};
class Okay {
 public:
  // For explanatory purposes. If Create() adds no value, it is better just
  // to have a public constructor instead.
  std::unique_ptr<Okay> Create() { return base::WrapUnique(new Okay()); }
  // ...

 private:
  Okay();
  // ...
};
  • 对于WrapUnique(new Foo) 和 WrapUnique(new Foo()) 来说,如果Foo没有自定义的构造函数的话那么这两者的含义是不同的,不要让未来的维护者猜测你是否是故意不写(),使用MakeUnique()来替换,如果你是有意不写()来作为优化,请加上说明
    auto a = base::WrapUnique(new A); // BAD: "()" omitted intentionally?
    auto a = base::MakeUnique<A>();   // GOOD

    // "()" intentionally omitted to avoid unnecessary zero-initialisation.
    // WrapUnique() does the wrong thing for array pointers.
    auto array = std::unique_ptr<A[]>(new A[size]);

不要使用auto来推导裸指针

​   鼓励使用auto来从初始化表达式中进行类型推导,但是当推导的是指针类型的时候不要使用auto,这会给使用者带来疑惑,应该使用auto*来替代,如下:

auto item = new Item();  // BAD: auto deduces to Item*, type of |item| is Item*
auto* item = new Item(); // GOOD: auto deduces to Item, type of |item| is Item*

正确使用const

​   不要从一个const成员方法中返回指针或对非const对象的引用,在这样的限制范围下,尽可能将方法标记为const的,避免使用const_cast去除const限制,除非要借助const的getter方法来实现非const的getter。这是因为C++中的const成员方法只是逻辑上的const,并不是真正意义上的const,为此我们必须在编码的过程中,遵守一定的最佳编码实践来正确的使用const,关于这个问题google的工程师开启了广泛的讨论,见Reference

Reference
1. Thoughts on duplicate const/non-const getters
2. Use_of_const

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值