C++ 将const std::unique_ptr用于pimpl习惯用法

C++ 将const std::unique_ptr用于pimpl习惯用法,c++,c++11,c++17,C++,C++11,C++17,在年,他建议用const std::unique\u ptr(大约10分钟)编写pimpl习语 对于移动构造函数/任务,这应该如何工作?c++17中有什么东西吗?我什么也找不到 这对移动构造函数/分配有何作用 : 如果以下任一条件为真,则类T的隐式声明或默认移动构造函数定义为已删除: T具有无法移动的非静态数据成员(具有已删除、不可访问或不明确的移动构造函数) const std::unique_ptr是这样一个数据成员,因为const 如果删除const,编译器将生成移动构造函数和赋值,但

在年,他建议用
const std::unique\u ptr
(大约10分钟)编写pimpl习语

对于移动构造函数/任务,这应该如何工作?c++17中有什么东西吗?我什么也找不到

这对移动构造函数/分配有何作用

:

如果以下任一条件为真,则类T的隐式声明或默认移动构造函数定义为已删除

  • T具有无法移动的非静态数据成员(具有已删除、不可访问或不明确的移动构造函数)
const std::unique_ptr
是这样一个数据成员,因为
const

如果删除
const
,编译器将生成移动构造函数和赋值,但不会生成复制构造函数和赋值


赫伯解释了他为什么使用
const unique\u ptr

非常量也可以工作,但它更脆弱,因为默认的移动语义可能不正确


对于
const
成员,它更健壮,因为
const
成员必须在构造函数中初始化。并且
const
记录了对象的实现没有改变,它不是状态或策略设计模式。

如果您的类应该永远不为空,则非const唯一的ptr(带有默认移动/分配)是不合适的。move ctor和move assign都将清空rhs

const unique ptr将禁用这些自动方法,如果您想要移动,则必须在impl中写入它(并在外部添加一点胶水)

我个人会用我想要的语义编写一个值ptr(然后让编译器编写胶水),但从const unique_ptr开始作为第一步听起来很合理

如果您放松了never-empty,并将其设置为几乎never-empty,那么您现在必须考虑许多方法的前提条件,以及可能的连锁反应bug


这项技术的最大成本,返回值的困难,在C++17中消失了。

那么在这种情况下,必须实现一个复制构造函数吗?@vordhosbn,如果有必要的话。这是真的,但我的问题是关于移动构造/赋值。使用普通的unique_ptr而不是const将使移动构造/分配保持活动状态,同时声明它为const禁止移动。我曾经认为const data members是一种糟糕的做法(因为这个问题),但显然Herb Sutter不这么认为。我想知道为什么。我会与萨特先生持相反的观点。
std::unique_ptr
上的
std::move
的默认行为是绝对正确的,这正是我们想要的-有效的状态转移,将moved from句柄保持在定义良好的状态-“无效,请勿触摸”。明确禁止移动是一回事(人们可能会对其动机感到惊讶)。对我来说,最重要的是通过构造来表达生命周期,并正确地使用默认值(在这种情况下,默认情况下没有脆性移动)。您可以自己编写复制和移动操作,例如,
MyClass&operator=(const MyClass&that){*pimpl=*that.pimpl;return*this;}
Impl
类仍然可以复制,甚至可以移动,您可以委托给它。当然,这只会让您获得95%的成功,而且也不反对编写自己的
value\u ptr
类型来自动执行深度复制/移动。为什么您不希望大多数pimpl类都是可移动的?这似乎是一件非常合理的事情。@DenisYaroshevskiy只能假设他心中有一个特定的用例。一般来说,我同意使用
unique\u ptr
作为pimpl的容器。如果你想让它成为可复制的,你就必须通过克隆操作来实现。只是看了视频的相关部分,我不同意他的观点。我认为对于大多数pimpl句柄来说,
const unique\u ptr
很快就会成为一个限制。@RichardHodges-Sutter是对的。如果使用pimpl复制一个类,则如果pimpl是共享的\u ptr,则有两个对象具有相同的pimpl;如果pimpl是唯一的\u ptr,则有一个对象没有pimpl。这打破了一个对象,一个pimpl的契约。另一方面,具有
const
pimpl的对象仍然可以移动。@SamVarshavchik我看不出
const unique\u ptr
是如何移动的。没有
const unique_ptr&
构造函数。如果你能提供示例代码,我会很感兴趣。我认为您必须编写一个移动构造函数来移动实现。这仍然使一个对象处于未定义状态。值得注意的是,他没有将
const unique_ptr
-作为pimpl(非)惯用法提供任何用例。赫伯是一个聪明的人,但他不一定是我会盲目追随的人。如果你从一个对象移出,它被认为是处于部分形成的状态,所以它只能被赋值和解构,这样移动构造函数/赋值就不会破坏你的非空保证。如果您愿意,您可以从gsl提出类似not_null的smth来包装它。@DenisYaroshevskiy不,它处于您愿意保证的任何状态?它不是“应该处于部分形成的状态”,除非这是您选择保证的。一种选择是几乎从不为空,并且只在从中移动时为空。这是一种选择,它有成本也有收益。从从不清空到几乎从不清空的成本不应该被忽略。从几乎从不空到从不空的成本也是如此。“永不为空”更容易保证正确性,与更快的代码相比,偏向更经常正确的代码通常是一个好主意。@Yakk即使从对象中移动的对象是“永不为空”,在移动后使用它也是一种强烈的代码气味。我认为,pimpl指针检查后加上断言/异常比允许人们依赖处于“默认”状态的moved from对象更可取