【问题标题】:How to switch between copy-constructor and default constructor depending on argument?如何根据参数在复制构造函数和默认构造函数之间切换?
【发布时间】:2016-03-29 19:56:33
【问题描述】:

我有以下代码:

struct S
{
    S(): a(42) {}
    int a;
};

class P
{
public:
    P(S const *s): m_s(s ? *s : /*init m_s by default ctor - how to achieve it?*/)
private:
    S m_s;
};

我希望 m_s 由复制构造函数或默认构造函数初始化,具体取决于指针 s

P p1(nullptr); // expect default ctor: p1.m_s.a = 42
S s;
s.a = 84;
P p2(&s); // expect copy ctor: p2.m_s.a = 84

我怎样才能以最优雅的方式做到这一点?

【问题讨论】:

  • 试试这个:m_s(s ? *s : S())。不是最有效的,但这就是使用指针所付出的代价。
  • 您可能无法以“最优雅的方式”进行操作。许多不优雅的解决方案同样在争夺第一名。
  • @n.m.为什么这不是最有效的?我希望编译器能够优化掉任何不必要的副本。
  • @ChrisDrew 我认为编译器不会优化显式调用的复制构造函数。不确定是否允许。
  • @n.m.它是允许的,并且将被优化。演示:goo.gl/XpVzVG

标签: c++ c++11 copy-constructor default-constructor


【解决方案1】:

你可以写:

class P {
public:
    P(S const *s): m_s(s ? *s : S()){}
private:
    S m_s;
};

您可能会发现将条件提取到单独的辅助函数更优雅:

S createS(S const *s) {
  return s ? *s : S();
}

nullptr 参数的情况下,这看起来会执行不必​​要的复制,但实际上编译器将执行 RVO 并优化复制。

Live demo.

【讨论】:

  • @Nick:这是更好的解决方案,你应该接受这个。
  • 对我来说实际上看起来不必要的副本是来自*s 的副本,因为您的条件表达式是一个右值(与*s 不同)——但那个也被省略了:-)
【解决方案2】:

仅适用于空指针常量的部分解决方案是添加重载:

P(std::nullptr_t) : m_s() {}
P(const S * s) : m_s(AssertNotNull(s)) {}

我在这里使用:

template <typename T> T * AssertNotNull(T * p) {
  if (!p) std::abort();
  return p;
}

如果需要动态开关,可以使用辅助函数:

struct P {
  static const S & maybe(const S * s) {
    static S x;
    return s ? *s : x;
  }

  P(const S * s) : m_s(maybe(s)) {}

  S m_s;
}

这实际上并没有在复制和默认构造函数之间切换(因为你不能动态地这样做),但是如果指针为空,它会通过从默认构造的实例复制来伪造效果。

【讨论】:

  • 为什么从maybe引用返回?如果您按值返回,编译器将优化掉任何副本,您将不需要那个令人不安的静态变量。事实上,当I test it 时,该静态变量实际上正在击败 RVO 并导致额外的副本。
  • @ChrisDrew:是的,这也是一种可能。
猜你喜欢
  • 2020-05-14
  • 2015-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-24
相关资源
最近更新 更多