-3

我正在编写一个C带有 type 成员foo的类foo_t。该成员必须在整个C实例的整个运行时间内定义且有效;但是,我没有在编译时构建它的必要信息,即我不能使用

class C {
    foo_t foo { arg };
}

我也不能在调用 ctor 时构造它C,即我不能

class C {
    foo_t foo;
    C (whatever) : foo(compute_arg(whatever)) { }
}

相反,我只能在Cctor 中的一些代码运行后才能构建它。编辑:这样做的原因可能是我需要运行一些具有副作用的代码(例如磁盘或网络 I/O)来获取构造参数;而且我还需要运行该代码才能初始化其他成员,因此我不能将其作为初始化列表中的自由函数多次调用。

那么,我应该如何表示foo

  • 如果foo_t可以用一些虚拟/无效/空/空值默认构造,那么我可以让它发生,并且知道它永远不会在那个虚拟状态下被访问是安全的。损害:fooin的声明C并不表明它总是有效的
  • 如果foo_t只有一个有效的状态,即在获得相关信息之前我根本无法构造它,那么:

    • 我可以使用std::unique_ptr<foo_t>;最初它将是nullptr,然后被分配给。缺点:没有迹象表明它在结束后永远不会为空C();无用的分配。
    • 我可以使用std::optional<foo_t>;最初它将是nullopt,然后被分配给。缺点:没有迹象表明它在结束后永远不会是空的C();需要 C++14;“可选”一词表明拥有 是“可选的” foo,而不是

我对第二种情况更感兴趣,因为在第一种情况下,关于 afoo_t有效性的歧义是一种内置的。有没有比我提到的两个更好的选择?

注意:我们无法更改foo_t.

4

2 回答 2

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 提出改进建议。

魔杖盒

于 2018-02-16T14:04:54.173 回答
1

通常,如果您可以foo_t在某个类的构造函数主体中构造 a(没有成员初始化列表),那么您可以修改您的代码,以便您的类现在具有一个foo_t属性,并且它的构造函数可以委托构造或在其成员初始化列表中构造它.

基本上,在大多数情况下,您可以重写有问题的构造函数,以便它委托给另一个构造函数,同时为它提供必要的信息以foo_t在成员初始化程序列表中构造一个实例(我在评论中使用以下“示例”快速而非正式地说明了这一点)https://ideone.com/ubbbb7


更一般地说,如果元组构造由于某种原因碰巧成为问题,则以下转换将(通常)起作用。诚然,它有点长(而且丑陋),但请记住,这是为了一般性,并且在实践中可能会简化事情。

假设我们有一个构造函数,我们在其中构造 a 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 的实际成员,并在最后一个构造函数的成员初始化列表中构造。

可以概括为任意数量的局部变量(和参数)。然而,我假设我们的初始构造函数没有任何成员初始值设定项列表。如果它有一个,我们可能需要:

  • 在它们发生变异之前复制一些 initialarg_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;
};

在那种情况下,我承认我不知道如何重写这个类,但我认为它很少见,并不重要。

于 2018-02-16T15:30:52.747 回答