构造函数初始化列表与昂贵的操作

时间:2016-10-19 17:34:37

标签: c++ constructor

考虑一个假设的场景,其中两个类可以默认构造或相互构造,但任何一种方式都被认为是昂贵的(以下是有意设计的例子):

struct PrivateKey;

struct PublicKey {
  PublicKey(); // generate a random public key (1 minute)
  PublicKey(const PrivateKey& b); // find a public key corresponding to a private key (1 year)
  ...members...
};

struct PrivateKey {
  PrivateKey(); // generate a random private key (1 minute)
  PrivateKey(const PublicKey& a); // find a private key corresponding to a public key (1 year)
  ...members...
};

(这当然可以浓缩为一个类,但问题的有效性不受影响。让我们说为了连贯性,彼此之间没有对称性。)

现在有一个结构可以保存两者的实例并需要进行交叉初始化。但是,我们可能需要两个方向,因此初始化程序列表无法真正删除它们(它们不是按列出的顺序运行,而是按成员定义的顺序运行,并且此处无法修复订单):

struct X {
  PublicKey a;
  PrivateKey b;
  X(int): a(), b(a) { }
  X(float): b(), a(b) { } // UB: a(b) happens before b is initialized
};

当然我可以试试:

struct X {
  PublicKey a;
  PrivateKey b;
  X(int): a(), b(a) { }
  X(float): a(), b() { a = PublicKey(b); }
};

但是这有多个问题,其中PublicKey的第二个构造函数中运行X的{​​{1}}的昂贵的默认构造只是为了立即抛出结果只是第一。 PublicKey::PublicKey()可能有副作用。两者仍然可以通过创建一个只暴露给朋友X的廉价私有构造函数来缓解,这会使该类处于某种虚拟状态,但会抛出一些引用或常量成员,并且该类可能无法移动 - 可分配或可交换的,禁止X::X(float)身上的任何变体。是否有更好的模式可以遵循?

2 个答案:

答案 0 :(得分:7)

通过使用指向包含的类的指针,而不是直接嵌入包含的类,并在构造函数体内自己构造包含的对象,可以避免构造顺序问题。

struct X {
  std::unique_ptr<PublicKey> a;
  std::unique_ptr<PrivateKey> b;
  X(int) {
    a = std::make_unique<PublicKey>();
    b = std::make_unique<PrivateKey>(*a);
  }
  X(float) {
    b = std::make_unique<PrivateKey>();
    a = std::make_unique<PublicKey>(*b);
  }
};

答案 1 :(得分:3)

如果这些类至少是可移动构造的,那么你应该可以这样做:

struct KeyPair
{
  PublicKey a;
  PrivateKey b;
  KeyPair(std::pair<PublicKey, PrivateKey> &&data) :
    a(std::move(data.first)),
    b(std::move(data.second))
  {}
};

std::pair<PublicKey, PrivateKey> computePublicFirst()
{
  PublicKey a;
  PrivateKey b(a);
  return {std::move(a), std::move(b)};
}

std::pair<PublicKey, PrivateKey> computePrivateFirst()
{
  PrivateKey b;
  PublicKey a(b);
  return {std::move(a), std::move(b)};
}

struct X
{
  KeyPair keys;
  X(int) : keys(computePublicFirst()) {}
  X(float) : keys(computePrivateFirst()) {}
};

没有移动任务发生,只移动施工。