【问题标题】:Is it possible to have a "named constructor" return a privately-constructed, non-moveable, non-copyable std::optional<T>?是否有可能让“命名构造函数”返回一个私有构造的、不可移动的、不可复制的 std::optional<T>?
【发布时间】:2021-01-13 00:49:54
【问题描述】:

我主要从事不允许抛出异常的系统级 C++ 项目,但(理所当然地)强烈鼓励使用 RAII。现在,我们使用许多 C++ 程序员都熟悉的臭名昭著的技巧来处理缺少构造函数失败的问题,例如:

  1. 简单的构造函数,然后调用 bool init(Args...) 来完成困难的工作
  2. 真正的构造函数后跟检查bool is_valid() const
  3. 使用static unique_ptr&lt;MyType&gt; create(Args...) 进行堆分配

当然,这些都有缺点(堆分配、无效和“移动”状态等)。

我的公司终于更新了编译器,将允许使用glorious C++17。由于 C++17 具有 std::optional&lt;T&gt; 以及最重要的强制复制省略,我希望我可以将我们所有的类大大简化为如下所示:

class MyType {
 public:
  static std::optional<MyType> create() {
    // If any of the hard stuff fails, return std::nullopt
    return std::optional<MyType>(std::in_place, 5, 'c');
  }
  ~MyType() {
    // Cleanup mArg0 and mArg1, which are always valid if the object exists
  }

  // ... class functionality ...

  // Disable default constructor, move, and copy.
  // None of these are needed because mandatory copy elision
  // allows the static function above to return rvalue without
  // copy or move operations
  MyType() = delete;

  MyType(const MyType&) = delete;
  MyType(MyType&&) = delete;

  MyType& operator=(const MyType&) = delete;
  MyType& operator=(MyType&&) = delete;

private:
  MyType(ArgT0 arg0, ArgT1 arg1) : mArg0(arg0), mArg1(arg1) {}
  ArgT0 mArg0;
  ArgT1 mArg1;
};

请注意这有多好:静态函数确保在创建对象之前完成所有困难的工作,缺少默认 ctor/move 意味着对象永远不会处于无效或移动状态,私有构造函数确保用户不会意外跳过命名的ctor。

不幸的是,由于 ctor 是私有的,std::is_constructable_t&lt;MyType&gt; 检查失败,因此 optionalin_place 构造函数被 SFINAE 淘汰。

如果我做两件事中的一件,这段代码就可以工作,但我都不想这样做:

  1. 将 ctor 公开(但现在该类的用户可能会意外绕过命名的 ctor)
  2. 允许移动操作(但现在我必须处理无效的对象)

我也试过这个,但它不起作用,因为std::optional 需要一个移动运算符才能工作:

static std::optional<MyType> create() {
  // If any of the hard stuff fails, return std::nullopt
  return std::optional<MyType>(MyType(5, 'c'));
}

我可能缺少一些技巧或咒语来让它工作,还是我达到了 C++17 允许的限制?

谢谢!

【问题讨论】:

  • 一个技巧是拥有一个public 构造函数,但带有一个private 标签类型的虚拟参数。然后该类看起来是可构造的,但实际上您只能在上下文中构造它一个成员工厂函数,如 create,其中可以实例化 private 标记类型。
  • @NicolBolas 我更希望得到有关如何解决它的建议。由于强制复制省略,在 C++17 中允许返回不可移动对象,但我似乎没有办法在不使用 in_place 并制作的情况下构造包含我的类型的 optional我的 ctor 公众。
  • @FrançoisAndrieux 谢谢! :) 如果事实证明没有其他方法可以让我的蛋糕也可以吃,至少这可以防止意外构建。
  • @something_clever:顺便说一句,即使在 C++17 之前,也有一种方法可以使用 return {..} Demo 返回不可移动类型。

标签: c++ c++17


【解决方案1】:

如果你想让任何间接对象构造工作(各种形式的emplaceoptionalin_place 构造函数、make_shared 等),所讨论的构造函数必须是公共的。您可以使用称为私钥的东西将构造函数公开,而不允许所有公开使用。

基本上,您创建一个类型(称为Key),其默认构造函数是私有的。该类没有成员,也不做任何事情。它声明MyTypeKey 的朋友;这意味着只有MyType 的成员才能构造一个。

现在,将MyType 的所有构造函数设为public,但它们都将Key const&amp; 作为第一个参数。这意味着理论上 任何人 都可以调用它们,但实际上只有拥有 Key 实例的人才能真正调用它们。 MyType 的成员可以创建这样的实例,他们可以将这些实例传递给optionalin_place 构造函数或任何其他间接机制。这有效地为间接构造机制提供了对构造函数的私有访问。

这是处理对类型的私有访问转发的标准习惯用法。事实上,可以假设编写一个通用的key&lt;T&gt; 类型,如下所示:

template<typename T>
class key
{
private:
  key() = default;
  key(int) {} //Not an aggregate

  friend T;
};

一张小纸条。由于 C++11 pre-C++20 的烦恼,除了默认/删除的复制/移动/默认构造函数之外,任何没有成员和构造函数的类型都被视为聚合。这是真的即使你明确地= default它的默认构造函数。因此,该类型可以进行聚合初始化,没有公有/私有区别。也就是说,任何人都可以通过以下方式调用您的私钥构造函数:MyType({}, &lt;params&gt;);

为避免这种情况,您需要为Key 提供一个额外的(私有)构造函数,或者以其他方式阻止它成为聚合。

【讨论】:

  • 很遗憾我们没有万能成语的可变参数朋友:(
猜你喜欢
  • 2018-02-10
  • 1970-01-01
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-04
相关资源
最近更新 更多