【发布时间】:2021-01-13 00:49:54
【问题描述】:
我主要从事不允许抛出异常的系统级 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 意味着对象永远不会处于无效或移动状态,私有构造函数确保用户不会意外跳过命名的ctor。
不幸的是,由于 ctor 是私有的,std::is_constructable_t<MyType> 检查失败,因此 optional 的 in_place 构造函数被 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 允许的限制?
谢谢!
【问题讨论】:
-
一个技巧是拥有一个
public构造函数,但带有一个private标签类型的虚拟参数。然后该类看起来是可构造的,但实际上您只能在上下文中构造它一个成员工厂函数,如create,其中可以实例化private标记类型。 -
@NicolBolas 我更希望得到有关如何解决它的建议。由于强制复制省略,在 C++17 中允许返回不可移动对象,但我似乎没有办法在不使用
in_place并制作的情况下构造包含我的类型的optional我的 ctor 公众。 -
@FrançoisAndrieux 谢谢! :) 如果事实证明没有其他方法可以让我的蛋糕也可以吃,至少这可以防止意外构建。
-
@something_clever:顺便说一句,即使在 C++17 之前,也有一种方法可以使用
return {..}Demo 返回不可移动类型。