我主要从事不允许抛出异常的系统级 C++ 项目,但(理所应当)强烈鼓励使用 RAII。现在,我们使用许多 C++ 程序员熟悉的臭名昭著的技巧来处理构造函数失败的问题,例如:
- 简单的构造函数,然后调用
bool init(Args...)
做困难的事情
- 真正的构造函数,然后进行检查
bool is_valid() const
- 堆分配
static unique_ptr<MyType> create(Args...)
当然,这些都有缺点(堆分配、无效和“移动”状态等)。
我的公司终于更新编译器并将允许glorious使用C++17。自 C++17 以来的特性std::optional<T>
而且,最重要的是,强制复制省略,我希望我可以大大简化我们所有的类,使其看起来像这样:
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 意味着对象永远不会以无效或移动状态存在,私有构造函数确保用户不会意外地跳过命名的演员。
不幸的是,由于演员是私人的,std::is_constructable_t<MyType>
检查失败,因此in_place
的构造函数optional
SFINAE 出局了。
如果我做了两件事之一,但我不想做这两件事,则此代码有效:
- 将 ctor 设为公开(但现在该类的用户可能会意外地绕过指定的 ctor)
- 允许移动操作(但现在我必须处理无效的对象)
我也尝试过这个,但它不起作用,因为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 允许的限制?
Thanks!