我正在写一个类C
,其成员foo
的类型为foo_t
。必须在C
实例的整个生命周期内定义此成员并使其有效;但是,我没有必要的信息来在编译时构建它,即我不能使用
class C {
foo_t foo { arg };
}
当C
的ctor被调用时,我也无法构建它,即我不能拥有
class C {
foo_t foo;
C (whatever) : foo(compute_arg(whatever)) { }
}
相反,我只能在C
ctor中的某些代码运行后构建它。 编辑:这可能是因为我需要运行一些带副作用的代码(例如磁盘或网络I / O)来获取构造参数;我还需要运行代码才能初始化其他成员,所以我不能只是从初始化列表中多次调用它作为自由函数。
那么,我该如何表示foo
?
foo_t
可以使用一些虚拟/无效/空/空值进行默认构造,那么我可以让它发生,并且知道它永远不会在该虚拟状态下被访问。 损害:foo
中C
的声明并不表示它始终有效。如果foo_t
只有一个有效状态,即在我掌握相关信息之前根本无法构建它,那么:
std::unique_ptr<foo_t>
;最初它将是nullptr
,然后被分配给。 损害:C()
结束后没有迹象表明它永远不会为空;无用的分配。 std::optional<foo_t>
;最初它将是nullopt
,然后被分配给。 损害:C()
结束后没有迹象表明它永远不会是空的;需要C ++ 14; “可选”一词表示拥有foo
是“可选的”,而不是。我对第二种情况更感兴趣,因为在第一种情况下,关于foo_t
有效性的模糊性是内置的。是否有更好的替代我提到的两个?
注意:我们无法改变foo_t
。
答案 0 :(得分:1)
让我们考虑更具体的案例
struct C {
foo_t foo1;
foo_t foo2;
C () :
foo1(read_from_file()),
foo2(read_from_file()),
{ }
static whatever_t read_from_file();
}
让我们假设不希望两次从文件中读取相同的数据。
一种可能的方法是:
struct C {
foo_t foo1;
foo_t foo2;
C(): C{Create()} {}
private:
static C Create()
{
return C{read_from_file()};
}
C(whatever_t whatever):
foo1{whatever},
foo2{whatever}
{}
static whatever_t read_from_file();
}
感谢@VittorioRomeo提出改进建议。
答案 1 :(得分:1)
通常,如果您可以在某个类的构造函数体中构造foo_t
(没有成员初始化列表),那么您可以修改代码,以便您的类现在具有foo_t
属性,它的构造函数委托构造或在其成员初始化列表中构造它。
基本上,在大多数情况下,您可以重写有问题的构造函数,以便它委托给另一个构造函数,同时为其提供必要的信息,以在成员初始化列表中构造foo_t
实例(我在下面快速非正式地说明了这一点)以下“示例”https://ideone.com/ubbbb7)
更一般地说,如果元组结构由于某种原因而成为问题,则以下转换(通常)将起作用。这无疑是有点长(而且丑陋),但请记住这是为了一般性的缘故,并且可能在实践中简化了事情。
假设我们有一个构造函数,我们构造一个foo_t
,为了简单起见,我们将进一步假设它具有以下形式:
C::C(T1 arg_1, T2 arg_2) {
side_effects(arg_1, arg_2);
TL1 local(arg_1, arg_2);
second_side_effects(arg_1, arg_2, local);
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
函数调用可能会改变参数。
我们可以委托一次消除构造函数体中local_1
的声明,然后再次删除对second_side_effects(arg_1, arg_2, local)
的调用。
C::C(T1 arg_1, T2 arg_2)
: C::C(arg_1, arg_2
,([](T1& a, T2& b){
side_effects(a, b);
}(arg_1, arg_2), TL1(a, b))) {}
C::C(T1& arg_1, T2& arg_2, TL1&& local)
: C::C(arg_1, arg_2
,[](T1& a, T2& b, TL1& c) -> TL1& {
second_side_effects(a, b, c);
return c;
}(arg_1, arg_2, local)) {}
C::C(T1& arg_1, T2& arg_2, TL1& local) {
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
显然,f
可以成为C的实际成员,并在最后一个构造函数的成员初始化列表中构建。
可以推广任意数量的局部变量(和参数)。但是我假设我们的初始构造函数没有任何成员初始化列表。如果有的话,我们可能需要:
arg_i
并在构造函数链中传递副本,以便它们最终可用于构建成员初始化列表中的其他成员如果由于某种原因,成员的构造函数会产生副作用,则必须选择后者。
然而,有一种情况是这一切都崩溃了。让我们考虑以下情况:#include <memory>
struct state_t; // non copyable, non movable
// irreversible function that mutates an instance of state_t
state_t& next_state(state_t&);
struct foo_t {
foo_t() = delete;
foo_t(const foo_t&) = delete;
foo_t(const state_t&);
};
// definitions are elsewhere
class C {
public:
struct x_first_tag {};
struct y_first_tag {};
// this constructor prevents us from reordering x and y
C(state_t& s, x_first_tag = {})
: x(new foo_t(s))
, y(next_state(s)) {}
// if x and y were both constructed in the member initializer list
// x would be constructed before y
// but the construction of y requires the original s which will
// be definitively lost when we're ready to construct x !
C(state_t& s, y_first_tag = {})
: x(nullptr)
, y(s) {
next_state(s);
x.reset(new foo_t(s));
}
private:
std::unique_ptr<foo_t> x; // can we make that a foo_t ?
foo_t y;
};
在那种情况下,我承认不知道如何改写这门课程,但我认为这种情况很少见,并不重要。