是否可能有一个“命名构造函数"?返回私有构造的,不可移动的,不可复制的std :: optional< T&gt ;? [英] Is it possible to have a "named constructor" return a privately-constructed, non-moveable, non-copyable std::optional<T>?

查看:64
本文介绍了是否可能有一个“命名构造函数"?返回私有构造的,不可移动的,不可复制的std :: optional< T&gt ;?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我主要从事不允许抛出异常的系统级C ++项目的工作,但强烈建议鼓励RAII.现在,我们使用许多C ++程序员熟悉的臭名昭著的技巧来解决构造函数失败的问题,例如:

I mostly work on system-level C++ projects that don't allow exceptions to be thrown, but RAII is (rightfully) strongly encouraged. Right now, we handle the lack of failing constructors using infamous tricks many C++ programmers are familiar with, like:

  1. 临时构造函数,然后调用 bool init(Args ...)来完成困难的工作
  2. 实际构造函数,然后检查 bool is_valid()const
  3. 使用 static unique_ptr< MyType>进行堆分配create(Args ...)
  1. Trivial constructor followed by a call to bool init(Args...) to do the hard stuff
  2. Real constructor followed by checking bool is_valid() const
  3. Heap-allocating with static unique_ptr<MyType> create(Args...)

当然,所有这些都有缺点(堆分配,无效和移动"状态等).

Of course, these all have drawbacks (heap allocation, invalid and "moved" states, etc).

我的公司最终将更新编译器,并将允许使用光荣的 C ++ 17.由于C ++ 17具有 std :: optional< T> ,最重要的是强制复制省略,所以我希望可以将我们所有的类大大简化为看起来像这样:

My company is finally updating compilers and will allow glorious C++17 to be used. Since C++17 features std::optional<T> and, most importantly, mandatory copy elision, I was hoping I could greatly simplify all our classes into something that would look like this:

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.

Notice how nice this is: Static function ensures all the hard stuff is done before the object is ever created, lack of default ctor/move means object never exists in an invalid or moved state, private constructor ensures user can't accidentally skip the named ctor.

不幸的是,由于ctor是私有的,因此 std :: is_constructable_t< MyType> 检查失败,因此 optional in_place 构造函数为SFINAE退出了.

Unfortunately, because the ctor is private, the std::is_constructable_t<MyType> check fails and therefore the in_place constructor of optional is SFINAE'd out.

如果我执行以下两项操作之一,而我都不想要执行以下操作,则此代码将起作用:

This code works if I do one of 2 things, neither of which I want to:

  1. 将ctor公开(但现在班级用户可以不小心绕过命名的ctor)
  2. 允许移动操作(但现在我必须处理无效的对象)

我也尝试过这种方法,但是它不起作用,因为 std :: optional 需要移动运算符才能起作用:

I have also tried this, but it doesn't work because std::optional required a move operator for this to work:

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

要使它正常工作,我可能会缺少一些窍门或咒语吗?还是我达到了C ++ 17允许的极限?

Is there some trick or incantation I may be missing to get this to work, or have I hit the limits of what C++17 will allow?

谢谢!

推荐答案

如果要进行任何间接的对象构造工作( emplace 以其各种形式,则 in_place 构造函数( optional make_shared 等),相关的构造函数必须是公共的.您可以通过使用称为私钥的方式将构造函数公开,而不允许所有公众使用.

If you want to make any indirect object construction work (emplace in its various forms, in_place constructors of optional, make_shared, etc) , the constructor in question must be public. You can make a constructor public without allowing all public use by using something called a private key.

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

Basically, you create a type (call it Key) whose default constructor is private. The class has no members, nor does it do anything. It declares that MyType is a friend of Key; this means that only members of MyType can construct one.

现在,将所有 MyType 的构造函数设为 public ,但是它们都将 Key const& 作为第一个参数.这意味着从理论上讲任何人都可以调用它们,但实际上只有具有 Key 实例的人才能真正调用它们.MyType 的成员可以创建这样的实例,并且可以将这些实例传递给 optional in_place 构造函数或任何其他间接机制.这有效地使间接构造机制可以私有访问构造函数.

Now, make all of MyType's constructors public, but they all take a Key const& as the first parameter. This means that in theory anyone could call them, but in practice only someone who has a Key instance can actually call them. Members of MyType can create such an instance, and they can pass those instances to optional's in_place constructor or any other indirect mechanism. This effectively gives the indirect construction mechanism private access to the constructor.

这是用于处理对类型的私有访问的转发的标准习语.确实,可以假设这样编写通用的 key< T> 类型:

This is a standard idiom for dealing with forwarding of private access to a type. Indeed, one could hypothetically write a generic key<T> type like this:

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

  friend T;
};

一个小纸条.由于C ++ 11 pre-C ++ 20的烦恼,任何没有成员且没有默认/删除的copy/move/default构造函数的构造函数的类型都被视为集合.即使您明确地 = default 其默认构造函数,这也是正确的.这样,该类型可以进行聚合初始化,而没有公共/私有区别.也就是说,任何人都可以通过执行以下操作来调用您的私钥构造函数: MyType({},< params>); .

One small note. Because of an annoyance of C++11 pre-C++20, any type with no members and no constructors other than defaulted/deleted copy/move/default constructors is considered an aggregate. This is true even if you explicitly = default its default constructor. As such, that type can undergo aggregate initialization, which has no public/private distinction. That is, anybody could call your private-key constructors by doing this: MyType({}, <params>);.

为避免这种情况,您将需要为 Key 提供一个额外的(私有)构造函数,否则将阻止它成为一个集合.

To avoid this, you will need to give Key an additional (private) constructor or otherwise prevent it from being an aggregate.

这篇关于是否可能有一个“命名构造函数"?返回私有构造的,不可移动的,不可复制的std :: optional&lt; T&gt ;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆