是否可能有一个“命名构造函数"?返回私有构造的,不可移动的,不可复制的std :: optional< T> ;? [英] Is it possible to have a "named constructor" return a privately-constructed, non-moveable, non-copyable std::optional<T>?
问题描述
我主要从事不允许抛出异常的系统级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:
- 临时构造函数,然后调用
bool init(Args ...)
来完成困难的工作 - 实际构造函数,然后检查
bool is_valid()const
- 使用
static unique_ptr< MyType>进行堆分配create(Args ...)
- Trivial constructor followed by a call to
bool init(Args...)
to do the hard stuff - Real constructor followed by checking
bool is_valid() const
- 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:
- 将ctor公开(但现在班级用户可以不小心绕过命名的ctor)
- 允许移动操作(但现在我必须处理无效的对象)
我也尝试过这种方法,但是它不起作用,因为 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< T> ;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!