Google C++每周贴士 #187: std::unique_ptr必须被移动

本文探讨了为何在C++中std::unique_ptr应体现所有权转移的概念,指出若对象从未被移动,可能不应使用std::unique_ptr。过度使用std::unique_ptr可能导致额外开销和不必要的复杂性。建议在不需要所有权转移的情况下使用栈分配或考虑其他智能指针,如absl::optional。同时,文章列举了std::unique_ptr的反模式并提供了改进方案。

(原文链接:https://abseil.io/tips/187 译者:clangpp@gmail.com)

每周贴士 #186: std::unique_ptr必须被移动

如果你说第一幕中有个std::unique_ptr挂在墙上,那么在第二幕或者第三幕中它必须被移动,不然就没必要挂在那。——对不住啦契诃夫

std::unique_ptr是用来表达所有权转移的。如果你不需要转移所有权到别处,那std::unique_ptr这层抽象基本既没用又不合适。

std::unique_ptr是个啥?

std::unique_ptr是个指针,并且在std::unique_ptr析构的时候释放掉它指向的对象。它的存在就是为了用类型系统表达所有权(释放资源的责任),这也是C++11最有价值的增量之一1。然而,std::unique_ptr常常被过度使用。一个立见分晓的检验办法是:如果它从没被std::move过,那么它基本上不该是个std::unique_ptr 如果不转移所有权,那么大多数情况下都有比使用std::unique_ptr更好的表达意图的方式。

std::unique_ptr的代价

不需要转移所有权的时候,有一些避免使用std::unique_ptr的原因:

  • 我们应该使用能最贴切地表达语义的类型。std::unique_ptr代表可转移的所有权,在不需要转移所有权的时候是多此一举。
  • std::unique_ptr可以为空。如果空状态没被用到,那给读者徒增认知烦恼。
  • std::unique_ptr管理堆上申请的T,这会影响性能:既有堆操作自身的代价,又包括数据分散在堆上所导致的降低数据进入CPU缓存的概率的损失。

常见的反模式(Anti-Pattern):避免&

常常看到如下的例子:

int ComputeValue() {
  auto data = absl::make_unique<Data>();
  ModifiesData(data.get());
  return data->GetValue();
}

在这个例子里,data不需要是std::unique_ptr,因为所有权从没转移。如果把它声明为栈上的Data对象,其构造和析构的位置都不带变的。因此,就像Tip #123也讨论过的那样,更好的选项是:

int ComputeValue() {
  Data data;
  ModifiesData(&data);
  return data.GetValue();
}

常见的反模式:延迟初始化

因为std::unique_ptr默认初始化为空,而且可以被absl::make_unique赋新值,所以常常见到std::unique_ptr被用来做延迟初始化的途径。这个模式特别常见于GoogleTest,在那里测试夹具(test fixtures)允许在SetUp里初始化对象。

class MyTest : public testing::Test {
 public:
  void SetUp() override {
    thing_ = absl::make_unique<Thing>(data_);
  }

 protected:
  Data data_;
  // 在`SetUp`中初始化,因此我们用`std::unique_ptr`作为延迟初始化的途径。
  std::unique_ptr<Thing> thing_;
};

跟前面一样,我们注意到thing_的所有权从没转移到别的地方,所以没必要使用std::unqiue_ptr。上面的例子本可以在MyTest的默认构造函数里执行所有的初始化。更多关于SetUp和构造的比较请参考GoogleTest FAQ

class MyTest : public testing::Test {
 public:
  MyTest() : thing_(data_) {}

 private:
  Data data_;
  Thing thing_;
};

在这个例子中,data_跟以前一样是默认构造的。然后,Thingdata_构造得来。请记住,类的构造函数按照数据成员的声明顺序构造它们,所以这种方式初始化对象的顺序和以前一样,只是去掉了std::unique_ptr

如果延迟初始化真的重要且不可避免,请考虑使用absl::optional和它的emplace()方法。Tip #123更深入地讨论了延迟初始化。

class MyTest : public testing::Test {
 public:
  MyTest() {
    Initialize(&data_);
    thing_.emplace(data_);
  }

 private:
  Data data_;
  absl::optional<Thing> thing_;
};

这可是C++,当然会有特殊情况:就算对象从没被移动过,使用std::unique_ptr也是合理的。但是这样的情况并不常见,并且任何处理此种情况的代码都应该用注释小心解释清楚。下面有两个这样的例子。

巨大,不常用的对象

如果一个对象只是有时候会用到,absl::optional是个不错的默认选项。然而,不管对象有没有真的被构造,absl::optional都会预先申请空间。如果空间很重要,也许使用std::unique_ptr且仅在需要时申请空间会是个合理的选择。

祖传API(Legacy APIs)

一些祖传API会返回裸指针指向拥有的数据(owned data)(译者注:需要接收者负责释放)。这些API通常是在std::unique_ptr被引入C++标准库之前写的,这种模式不应该在新代码中被复制。然而,就算结果对象从没被移动过,这些祖传API调用也应该包裹一层std::unique_ptr,以保证内存不会被泄露。

Widget *CreateLegacyWidget() { return new Widget; }

int func() {
  Widget *w = CreateLegacyWidget();
  return w->num_gadgets();
}  // 内存泄露!

给对象包裹一层std::unique_ptr解决了以上所有问题:

int func() {
  std::unique_ptr<Widget> w(CreateLegacyWidget());
  return w->num_gadgets();
}  // `w`被正确地析构了

  1. std::unique_ptr以“unique”(译者注:独有)命名就是为了说明没有其他的std::unique_ptr应该持有相同的非空值。也就是说,在程序执行的任意时刻,在所有非空的std::unique_ptr中,所有std::unique_ptr持有的地址都各不相同。 ↩︎

<think>我们正在解决的问题是:在C++代码中使用std::unique_ptr时显示没有unique_ptr类对象。 根据之前的引用,我们知道std::unique_ptrC++11引入的智能指针,位于<memory>头文件中。 可能的原因及解决方法: 1. 没有包含<memory>头文件:确保代码中包含了#include <memory>。 2. 编译器不支持C++11或更高版本:需要确保编译器启用了C++11或更高标准。在编译时加上-std=c++11或更高版本(如-std=c++14、-std=c++17)的选项。 3. 使用了错误的命名空间:std::unique_ptrstd命名空间中,如果使用了using namespace std;则可以直接使用,否则必须使用std::unique_ptr。 下面我们将详细讲解: 第一步:包含头文件 在需要使用std::unique_ptr的源文件中,确保包含了<memory>头文件。 第二步:检查编译器选项 在CMakeLists.txt中,我们可以设置C++标准。例如: set(CMAKE_CXX_STANDARD 11) # 或者更高 set(CMAKE_CXX_STANDARD_REQUIRED ON) 第三步:正确使用命名空间 例如: std::unique_ptr<int> ptr = std::make_unique<int>(10); 如果上述步骤都正确,那么问题应该解决。 示例代码: 假设我们有一个简单的程序,使用std::unique_ptr: ```cpp #include <iostream> #include <memory> // 必须包含这个头文件 int main() { // 使用make_unique创建unique_ptrC++14起,但C++11中可以使用new) #if __cplusplus >= 201402L std::unique_ptr<int> ptr = std::make_unique<int>(10); #else std::unique_ptr<int> ptr(new int(10)); #endif std::cout << *ptr << std::endl; return 0; } ``` 在CMakeLists.txt中,我们需要设置C++标准: ```cmake cmake_minimum_required(VERSION 3.1) project(unique_ptr_example) set(CMAKE_CXX_STANDARD 11) # 或者14、17等 set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(unique_ptr_example main.cpp) ``` 如果编译器不支持C++11,则可能需要升级编译器。 另外,注意std::make_uniqueC++14引入的,在C++11中需要使用new来初始化。因此,如果项目必须使用C++11,则应使用new的方式(如上面示例中的#else部分)。 如果问题仍然存在,请检查编译器的版本和是否支持C++11及以上。 下面是对应的问题列表: </think>### 解决C++中`std::unique_ptr`显示未定义的问题 在C++代码中使用`std::unique_ptr`时出现"未定义类对象"错误的主要原因及解决方法如下: #### 1. **未包含必需的头文件** `std::unique_ptr`定义在`<memory>`头文件中: ```cpp #include <memory> // 必须包含此头文件[^1][^2][^3] ``` 缺少此包含会导致编译器无法识别`unique_ptr`类。 #### 2. **未启用C++11或更高标准** `std::unique_ptr`是C++11引入的特性: - **编译器选项**: ```bash g++ -std=c++11 your_file.cpp # GCC/Clang cl /std:c++11 your_file.cpp # MSVC ``` - **CMake解决方案**: ```cmake set(CMAKE_CXX_STANDARD 11) # 启用C++11 set(CMAKE_CXX_STANDARD_REQUIRED ON) ``` #### 3. **命名空间使用错误** ```cpp // 错误示例:缺少std命名空间 unique_ptr<int> ptr; // 正确写法: std::unique_ptr<int> ptr = std::make_unique<int>(10); // 显式使用std[^3] ``` #### 4. **编译器版本不兼容** - 确认编译器支持C++11: - GCC ≥ 4.8,Clang ≥ 3.3,MSVC ≥ 2013 - 检查编译器版本: ```bash g++ --version # 查看GCC版本 ``` #### 完整修复示例 ```cpp #include <iostream> #include <memory> // 关键头文件 int main() { // 明确使用std命名空间 std::unique_ptr<int> ptr = std::make_unique<int>(42); std::cout << "Value: " << *ptr << std::endl; return 0; } ``` ```cmake # CMake修复方案 cmake_minimum_required(VERSION 3.12) project(FixedExample) set(CMAKE_CXX_STANDARD 11) # 启用C++11 set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(fixed_app main.cpp) ``` ### 常见错误场景分析 | **错误类型** | **典型错误提示** | **解决方案** | |-------------|-----------------|-------------| | 头文件缺失 | `&#39;unique_ptr&#39; is not a member of &#39;std&#39;` | 添加 `#include <memory>` | | C++标准未启用 | `&#39;make_unique&#39; was not declared in this scope` | 编译器添加 `-std=c++11` | | 命名空间错误 | `&#39;unique_ptr&#39; was not declared in this scope` | 使用 `std::unique_ptr` | | 编译器过旧 | `error: &#39;make_unique&#39; is not a member of &#39;std&#39;` | 升级编译器版本 | ### 相关问题 1. `std::shared_ptr`与`std::unique_ptr`在内存管理上有何本质区别?[^1][^2] 2. 如何为自定义类实现`std::unique_ptr`的安全删除器?[^2][^3] 3. 在哪些场景下使用`std::make_unique`比直接调用`new`更安全?[^2][^3] 4. 如何检测项目中的C++标准支持级别? 5. 在多线程环境中使用`std::unique_ptr`需要注意哪些线程安全问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值