首页 > 编程 > C++ > 正文

C++ Dos and Don'ts

2019-11-08 19:45:38
字体:
来源:转载
供稿:网友

最小化头文件

不要包含不必要的头文件

尽量使用前向声明的方式,目的是为了减少编译时间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实现细节的。使用匿名空间的好处就是可以限制对象的作用域在本文件中。

Why are unnamed namespaces used and what are their benefits?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; }};

停止对虚方法进行内联

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

Can virtual functions be inlined?

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

在头文件外可以放什么代码?

​ TODO

静态变量

​ 动态初始化函数作用域内的静态变量在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(…) 和 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*
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选