每周技巧 #188:小心智能指针函数参数

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #188: Be Careful With Smart-Pointer Function Parameters

原文最初作为 TotW #188 发布于 2020 年 12 月 10 日。

作者:Krzysztof Kosiński

更新于 2020 年 12 月 10 日。

快捷链接:abseil.io/tips/188

下面这段代码有什么问题?

1
2
3
4
bool CanYouPetTheDog(const std::shared_ptr<Dog>& dog,
                     absl::Duration min_delay) {
  return dog->GetLastPetTime() + min_delay < absl::Now();
}

函数 CanYouPetTheDog 并不影响 dog 参数的所有权,但它的签名要求 dog 必须存储在 std::shared_ptr 中。这造成了对特定所有权模型的不必要依赖,尽管函数中没有任何东西需要它。这种依赖会阻止调用者使用其他模型,例如 std::unique_ptr,或者在栈上构造对象。

所有权不受影响时使用引用或指针

通过使用引用,我们可以移除对特定所有权模型的依赖,并让函数可用于任何 Dog 类型对象。

1
2
3
bool CanYouPetTheDog(const Dog& dog, absl::Duration min_delay) {
  return dog.GetLastPetTime() + min_delay < absl::Now();
}

有了上面的定义,无论调用者使用什么所有权模型,都可以调用这个函数:

1
2
3
4
5
6
7
8
Dog stack_dog;
if (CanYouPetTheDog(stack_dog, delay)) { ... }

auto heap_dog = std::make_unique<Dog>();
if (CanYouPetTheDog(*heap_dog, delay)) { ... }

CustomPetPtr<Dog> custom_dog = CreateDog();
if (CanYouPetTheDog(*custom_dog, delay)) { ... }

如果函数会修改传入的值,请传递可变引用或原始指针,并使用与上面相同的惯用法。

函数修改所有权时使用智能指针

下面代码为不同智能指针参数提供了几个重载。第一个重载接管传入对象的所有权,第二个重载为传入对象添加共享引用。这两个操作都依赖调用者如何处理 Dog 的所有权。收养一个活在栈上的 Dog 是不可能的,因为不能从栈上拿走所有权。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Human {
 public:
  ...
  // 将 `dog` 的所有权转移给这个 Human。
  // 关于按值接受 std::unique_ptr 的理由,见技巧 #117。
  void Adopt(std::unique_ptr<Dog> dog) {
    pets_.push_back(std::move(dog));
  }
  // 为 `cat` 添加一个共享引用。
  void Adopt(std::shared_ptr<Cat> cat) {
    pets_.push_back(std::move(cat));
  }

 private:
  std::vector<std::shared_ptr<Pet>> pets_;
  ...
};

结论

如果所有权没有被转移或修改,请避免把智能指针作为函数参数。

相关阅读

每周技巧 #187:`std::unique_ptr` 必须被移动

上一节

每周技巧 #197:读锁应当少见

下一节