本文是Abseil库 tip of the week的总结。不是翻译,有些点子还是不错的。


totw #1 string_view

厌烦了const char*到string之间的处理转换?你只是想用一下而已不需要构造一个拷贝?string_view就是为此而生的,它是一个视图,就是一个借用,也是类似go rust胖指针 slice之类的东西。内部有一个指针和一个长度

注意,string_view是没有\0的

totw #3 String Concatenation and operator+ vs. StrCat()

简单说,不要用string::operator +() 会有临时变量。absl::StrCat用来解决这个问题

totw #10 Splitting Strings, not Hairs

absl提供了string split相关函数

// Splits on commas. Stores in vector of string_view (no copies).
std::vector<absl::string_view> v = absl::StrSplit("a,b,c", ',');

// Splits on commas. Stores in vector of string (data copied once).
std::vector<std::string> v = absl::StrSplit("a,b,c", ',');

// Splits on literal string "=>" (not either of "=" or ">")
std::vector<absl::string_view> v = absl::StrSplit("a=>b=>c", "=>");

// Splits on any of the given characters (',' or ';')
using absl::ByAnyChar;
std::vector<std::string> v = absl::StrSplit("a,b;c", ByAnyChar(",;"));

// Stores in various containers (also works w/ absl::string_view)
std::set<std::string> s = absl::StrSplit("a,b,c", ',');
std::multiset<std::string> s = absl::StrSplit("a,b,c", ',');
std::list<std::string> li = absl::StrSplit("a,b,c", ',');

// Equiv. to the mythical SplitStringViewToDequeOfStringAllowEmpty()
std::deque<std::string> d = absl::StrSplit("a,b,c", ',');

// Yields "a"->"1", "b"->"2", "c"->"3"
std::map<std::string, std::string> m = absl::StrSplit("a,1,b,2,c,3", ',');

要是c++有python那种split就好了。(那种效率比较低)

totw #11 RVO 返回值优化。返回局部变量不必考虑多余的拷贝构造等。现代编译器默认功能
totw #42: Prefer Factory Functions to Initializer Methods

google是禁止使用异常的。如果初始化会失败,那就用工厂函数来搞,大概这样

class Foo {
 public:
  // Factory method: creates and returns a Foo.
  // May return null on failure.
  static std::unique_ptr<Foo> Create();

  // Foo is not copyable.
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;

 private:
  // Clients can't invoke the constructor directly.
  Foo();
};

std::unique_ptr<Foo> Foo::Create() {
  // Note that since Foo's constructor is private, we have to use new.
  return absl::WrapUnique(new Foo());
}

这里注意,std::unique_ptr不能访问private构造函数,所以absl提供了一个wrapunique的一个wrapper

totw #45 不要用全局变量,尤其是库代码

(只能说尽量啦)

totw #88: Initialization: =, (), and {}

总结好了

  • 简单的构造逻辑,直接用=初始化基本类型(结合{}),结构体,以及拷贝构造

    int x = 2;
    std::string foo = "Hello World";
    std::vector<int> v = {1, 2, 3};
    std::unique_ptr<Matrix> matrix = NewMatrix(rows, cols);
    MyStruct x = {true, 5.0};
    MyProto copied_proto = original_proto;
    
    // Bad code
    int x{2};
    std::string foo{"Hello World"};
    std::vector<int> v{1, 2, 3};
    std::unique_ptr<Matrix> matrix{NewMatrix(rows, cols)};
    MyStruct x{true, 5.0};
    MyProto copied_proto{original_proto};
    
  • 用传统的构造函数语义()来调用复杂的构造语义

    Frobber frobber(size, &bazzer_to_duplicate);
    std::vector<double> fifty_pies(50, 3.14);
    // Bad code 
    // Could invoke an intializer list constructor, or a two-argument   constructor.
    Frobber frobber{size, &bazzer_to_duplicate};
    
    // Makes a vector of two doubles.
    std::vector<double> fifty_pies{50, 3.14};
    
  • {}不用=这种场景, 只用在上面这两种场景不能编译的情况下

    class Foo {
     public:
      Foo(int a, int b, int c) : array_{a, b, c} {}
      
     private:
      int array_[5];
      // Requires {}s because the constructor is marked explicit
      // and the type is non-copyable.
      EventManager em{EventManager::Options()};
    };
    
  • {}auto不要混用

    // Bad code
    auto x{1};
    auto y = {2}; // This is a std::initializer_list<int>!
    
totw #93: using absl::Span 也就是std::span ,container_view,更好用。也可以理解成flat pointer
totw #117: Copy Elision and Pass-by-value

就是一种吃掉拷贝的优化

// First constructor version
explicit Widget(const std::string& name) : name_(name) {}

// Second constructor version
explicit Widget(std::string name) : name_(std::move(name)) {}
totw #120: Return Values are Untouchable
MyStatus DoSomething() {
  MyStatus status;
  auto log_on_error = RunWhenOutOfScope([&status] {
    if (!status.ok()) LOG(ERROR) << status;
  });
  status = DoA();
  if (!status.ok()) return status;
  status = DoB();
  if (!status.ok()) return status;
  status = DoC();
  if (!status.ok()) return status;
  return status;
}

这段代码是有问题的,lambda中访问的status是在return执行完之后才会析构访问的,但是鉴于返回值优化,return没有执行,勉强正确,如果最后一行换成return MyStatus(); 这个lambda将永远错误的status,行为是未定义的 ,可能是错误的逻辑

解决办法,不用这种RAII访问返回值

totw #123: absl::optional and std::unique_ptr

这个表概括的很好了,absl::optional就是std::optional的替代版

  Bar absl::optional<Bar> std::unique_ptr<Bar>
Supports delayed construction  
Always safe to access    
Can transfer ownership of Bar    
Can store subclasses of Bar    
Movable If Bar is movable If Bar is movable
Copyable If Bar is copyable If Bar is copyable  
Friendly to CPU caches  
No heap allocation overhead  
Memory usage sizeof(Bar) sizeof(Bar) + sizeof(bool)2 sizeof(Bar*) when null, sizeof(Bar*) + sizeof(Bar) otherwise
Object lifetime Same as enclosing scope Restricted to enclosing scope Unrestricted
Call f(Bar*) f(&val_) f(&opt_.value()) or f(&*opt_) f(ptr_.get()) or f(&*ptr_)
Remove value N/A opt_.reset(); or opt_ = absl::nullopt; ptr_.reset(); or ptr_ = nullptr;
totw#126: make_unique is the new new
How Should We Choose Which to Use?
  1. By default, use absl::make_unique() (or std::make_shared() for the rare cases where shared ownership is appropriate) for dynamic allocation. For example, instead of: std::unique_ptr<T> bar(new T()); write auto bar = absl::make_unique<T>(); and instead of bar.reset(new T()); write bar = absl::make_unique<T>();
  2. In a factory function that uses a non-public constructor, return a std::unique_ptr<T> and use absl::WrapUnique(new T(...)) in the implementation.
  3. When dynamically allocating an object that requires brace initialization (typically a struct, an array, or a container), use absl::WrapUnique(new T{...}).
  4. When calling a legacy API that accepts ownership via a T*, either allocate the object in advance with absl::make_unique and call ptr.release() in the call, or use new directly in the function argument.
  5. When calling a legacy API that returns ownership via a T*, immediately construct a smart pointer with WrapUnique (unless you’re immediately passing the pointer to another legacy API that accepts ownership via a T*).
Summary

Prefer absl::make_unique() over absl::WrapUnique(), and prefer absl::WrapUnique() over raw new.

totw #131: Special Member Functions and = default

Prefer =default over writing an equivalent implementation by hand, even if that implementation is just {}

totw#134: make_unique and private constructors

和42条一样场景,make_unique不能直接创建private 构造函数,几个办法

声明成友元函数,或者用个wrapunique,或者替换成shared_ptr

totw #141: Beware Implicit Conversions to bool

bool转换的问题。属于老生常谈了。特定类型就需要实现safe bool,一般类型用option<T>包装一层 absl::optional<T>::has_value()判断

Tote #144 : Heterogeneous Lookup in Associative Containers

看代码

struct StringCmp {
  using is_transparent = void;
  bool operator()(absl::string_view a, absl::string_view b) const {
    return a < b;
  }
};

std::map<std::string, int, StringCmp> m = ...;
absl::string_view some_key = ...;
// The comparator `StringCmp` will accept any type that is implicitly
// convertible to `absl::string_view` and says so by declaring the
// `is_transparent` tag.
// We can pass `some_key` to `find()` without converting it first to
// `std::string`. In this case, that avoids the unnecessary memory allocation
// required to construct the `std::string` instance.
auto it = m.find(some_key);

省一个拷贝

再比如

struct ThreadCmp {
  using is_transparent = void;
  // Regular overload.
  bool operator()(const std::thread& a, const std::thread& b) const {
    return a.get_id() < b.get_id();
  }
  // Transparent overloads
  bool operator()(const std::thread& a, std::thread::id b) const {
    return a.get_id() < b;
  }
  bool operator()(std::thread::id a, const std::thread& b) const {
    return a < b.get_id();
  }
  bool operator()(std::thread::id a, std::thread::id b) const {
    return a < b;
  }
};

std::set<std::thread, ThreadCmp> threads = ...;
// Can't construct an instance of `std::thread` with the same id, just to do the lookup.
// But we can look up by id instead.
std::thread::id id = ...;
auto it = threads.find(id);
totw #149 Object Lifetimes vs. =delete

不只是构造函数析构函数可以标记为delete,普通成员函数也可以标记delete。这就引入了复杂性问题,是让类更完备还是更复杂

=delete for Lifetimes

假如你标记为delete就是为了限制参数的生存周期

class Request {
  ...

  // The provided Context must live as long as the current Request.
  void SetContext(const Context& context);
  void SetContext(Context&& context) = delete;

但是报错并不能告诉你正确的做法,只会告诉你因为什么错了

error: call to deleted function 'SetContext'

  SetContext(Context{});
  ^~~~~~~~~~

<source>:4:6: note: candidate function has been explicitly deleted

void SetContext(Context&& context) = delete;

你看了看报错,决定改一下,但是为什么这样改,不知道

这种设计会避免bug,但是也需要完善注释,不然不知所以

要是编译时报错能够定制错误信息,就牛逼了。

=delete for “Optimization”

这个场景是上面的反面,假如只接受右值,不接受拷贝,这样所有调用的时候都得显式加上std::move,显然更麻烦

这样做实际上是复杂化了,totw不建议使用,keep it simple

Tip of the Week #152 : AbslHashValue and You

把内部用的hash算法暴露出来了,可以这么用

struct Song {
  std::string name;
  std::string artist;
  absl::Duration duration;

  template <typename H>
  friend H AbslHashValue(H h, const Song& s) {
    return H::combine(std::move(h), s.name, s.artist, s.duration);
  }

  // operator == and != omitted for brevity.
};

Tip of the Week #161: Good Locals and Bad Locals

Good

auto& subsubmessage = *myproto.mutable_submessage()->mutable_subsubmessage();
subsubmessage.set_foo(21);
subsubmessage.set_bar(42);
subsubmessage.set_baz(63);

Bad

MyType value = SomeExpression(args);
return value;

Tip of the Week #171: Avoid Sentinel Values

返回值不清晰,建议用optional或者status

Tip of the Week #173: Wrapping Arguments in Option Structs

多参数构造抽象成option,不然难维护,构造指定不合理

Tip of the Week #175: Changes to Literal Constants in C++14 and C++17.

尽可能用1’000’000’000

以及chrono的ms min

Tip of the Week #176: Prefer Return Values to Output Parameters

尽可能用返回值。如果出参需要非常灵活,返回值控制不了,就用出参

Tip of the Week #181: Accessing the value of a StatusOr<T>

就是rocksdb status那种东西

// The same pattern used when handling a unique_ptr...
std::unique_ptr<Foo> foo = TryAllocateFoo();
if (foo != nullptr) {
  foo->DoBar();  // use the value object
}

// ...or an optional value...
absl::optional<Foo> foo = MaybeFindFoo();
if (foo.has_value()) {
  foo->DoBar();
}

// ...is also ideal for handling a StatusOr.
absl::StatusOr<Foo> foo = TryCreateFoo();
if (foo.ok()) {
  foo->DoBar();
}

表达能力比option和expect要好一些

此外,abseil还支持解析参数,支持gflags,可以说是把gflags迁移到这个库了,header only更友好一些


ref

  1. https://abseil.io/tips/